<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
先從一個例子開始:
public class LambdaTest { public static void print(String name, Print print) { print.print(name); } public static void main(String[] args) { String name = "Chen Longfei"; String prefix = "hello, "; print(name, (t) -> System.out.println(t)); // 與上一行不同的是,Lambda表示式的函數體中參照了外部變數‘prefix' print(name, (t) -> System.out.println(prefix + t)); } } @FunctionalInterface interface Print { void print(String name); }
例子很簡單,定義了一個函數式介面Print ,main方法中有兩處程式碼以Lambda表示式的方式實現了print介面,分別列印出不帶字首與帶字首的名字。
執行程式,列印結果如下:
Chen Longfei
hello, Chen Longfei
而(t) -> System.out.println(t)與(t) -> System.out.println(prefix + t))之類的Lambda表示式到底是怎樣被編譯與呼叫的呢?
我們知道,編譯器編譯Java程式碼時經常在背地裡“搞鬼”比如類的全限定名的補全,泛型的型別推斷等,編譯器耍的這些小聰明可以幫助我們寫出更優雅、簡潔、高效的程式碼。鑑於編譯器的一貫作風,我們有理由懷疑,新穎而另類的Lambda表示式在編譯時很可能會被改造過了。
下面通過javap反編譯class檔案一探究竟。 javap是jdk自帶的一個位元組碼檢視工具及反編譯工具: 用法: javap 其中, 可能的選項包括:
-help --help -? 輸出此用法訊息
-version 版本資訊
-v -verbose 輸出附加資訊
-l 輸出行號和本地變數表
-public 僅顯示公共類和成員
-protected 顯示受保護的/公共類和成員
-package 顯示程式包/受保護的/公共類
和成員 (預設)
-p -private 顯示所有類和成員
-c 對程式碼進行反組合
-s 輸出內部型別簽名
-sysinfo 顯示正在處理的類的
系統資訊 (路徑, 大小, 日期, MD5 雜湊)
-constants 顯示最終常數
-classpath <path> 指定查詢使用者類檔案的位置
-cp <path> 指定查詢使用者類檔案的位置
-bootclasspath <path> 覆蓋引導類檔案的位置
結果如下:
javap -p Print.class
interface test.Print { public abstract void print(java.lang.String); }
// Compiled from "LambdaTest.java" public class test.LambdaTest { public test.LambdaTest(); public static void print(java.lang.String, test.Print); public static void main(java.lang.String[]); private static void Lambda$main$1(java.lang.String); private static void Lambda$main$0(java.lang.String, java.lang.String); }
可見,編譯器對Print介面的改造比較小,只是為print方法新增了public abstract關鍵字,而對LambdaTest的變化就比較大了,新增了兩個靜態方法:
private static void Lambda$main$1(java.lang.String); private static void Lambda$main$0(java.lang.String, java.lang.String);
到底有什麼關聯呢?使用javap -p -v -c LambdaTest.class檢視更加詳細的反編譯結果:
public class test.LambdaTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #15.#30 // java/lang/Object."<init>":()V #2 = InterfaceMethodref #31.#32 // test/Print.print:(Ljava/lang/String;)V #3 = String #33 // Chen Longfei #4 = String #34 // hello, #5 = InvokeDynamic #0:#39 // #0:print:(Ljava/lang/String;)Ltest/Print; #6 = Methodref #14.#40 // test/LambdaTest.print:(Ljava/lang/String;Ltest/Print;)V #7 = InvokeDynamic #1:#42 // #1:print:()Ltest/Print; #8 = Fieldref #43.#44 // java/lang/System.out:Ljava/io/PrintStream; #9 = Methodref #45.#46 // java/io/PrintStream.println:(Ljava/lang/String;)V #10 = Class #47 // java/lang/StringBuilder #11 = Methodref #10.#30 // java/lang/StringBuilder."<init>":()V #12 = Methodref #10.#48 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder ; #13 = Methodref #10.#49 // java/lang/StringBuilder.toString:()Ljava/lang/String; #14 = Class #50 // test/LambdaTest #15 = Class #51 // java/lang/Object #16 = Utf8 <init> #17 = Utf8 ()V #18 = Utf8 Code #19 = Utf8 LineNumberTable #20 = Utf8 print #21 = Utf8 (Ljava/lang/String;Ltest/Print;)V #22 = Utf8 main #23 = Utf8 ([Ljava/lang/String;)V #24 = Utf8 Lambda$main$1 #25 = Utf8 (Ljava/lang/String;)V #26 = Utf8 Lambda$main$0 #27 = Utf8 (Ljava/lang/String;Ljava/lang/String;)V #28 = Utf8 SourceFile #29 = Utf8 LambdaTest.java #30 = NameAndType #16:#17 // "<init>":()V #31 = Class #52 // test/Print #32 = NameAndType #20:#25 // print:(Ljava/lang/String;)V #33 = Utf8 Chen Longfei #34 = Utf8 hello, #35 = Utf8 BootstrapMethods #36 = MethodHandle #6:#53 // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/inv oke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/M ethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #37 = MethodType #25 // (Ljava/lang/String;)V #38 = MethodHandle #6:#54 // invokestatic test/LambdaTest.Lambda$main$0:(Ljava/lang/String;Ljava/lang/St ring;)V #39 = NameAndType #20:#55 // print:(Ljava/lang/String;)Ltest/Print; #40 = NameAndType #20:#21 // print:(Ljava/lang/String;Ltest/Print;)V #41 = MethodHandle #6:#56 // invokestatic test/LambdaTest.Lambda$main$1:(Ljava/lang/String;)V #42 = NameAndType #20:#57 // print:()Ltest/Print; #43 = Class #58 // java/lang/System #44 = NameAndType #59:#60 // out:Ljava/io/PrintStream; #45 = Class #61 // java/io/PrintStream #46 = NameAndType #62:#25 // println:(Ljava/lang/String;)V #47 = Utf8 java/lang/StringBuilder #48 = NameAndType #63:#64 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #49 = NameAndType #65:#66 // toString:()Ljava/lang/String; #50 = Utf8 test/LambdaTest #51 = Utf8 java/lang/Object #52 = Utf8 test/Print #53 = Methodref #67.#68 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHan dles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;L java/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #54 = Methodref #14.#69 // test/LambdaTest.Lambda$main$0:(Ljava/lang/String;Ljava/lang/String;)V #55 = Utf8 (Ljava/lang/String;)Ltest/Print; #56 = Methodref #14.#70 // test/LambdaTest.Lambda$main$1:(Ljava/lang/String;)V #57 = Utf8 ()Ltest/Print; #58 = Utf8 java/lang/System #59 = Utf8 out #60 = Utf8 Ljava/io/PrintStream; #61 = Utf8 java/io/PrintStream #62 = Utf8 println #63 = Utf8 append #64 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #65 = Utf8 toString #66 = Utf8 ()Ljava/lang/String; #67 = Class #71 // java/lang/invoke/LambdaMetafactory #68 = NameAndType #72:#76 // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava /lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/ lang/invoke/CallSite; #69 = NameAndType #26:#27 // Lambda$main$0:(Ljava/lang/String;Ljava/lang/String;)V #70 = NameAndType #24:#25 // Lambda$main$1:(Ljava/lang/String;)V #71 = Utf8 java/lang/invoke/LambdaMetafactory #72 = Utf8 metafactory #73 = Class #78 // java/lang/invoke/MethodHandles$Lookup #74 = Utf8 Lookup #75 = Utf8 InnerClasses #76 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/ lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #77 = Class #79 // java/lang/invoke/MethodHandles #78 = Utf8 java/lang/invoke/MethodHandles$Lookup #79 = Utf8 java/lang/invoke/MethodHandles { public test.LambdaTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 public static void print(java.lang.String, test.Print); descriptor: (Ljava/lang/String;Ltest/Print;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=2 0: aload_1 1: aload_0 2: invokeinterface #2, 2 // InterfaceMethod test/Print.print:(Ljava/lang/String;)V 7: return LineNumberTable: line 9: 0 line 10: 7 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #3 // String Chen Longfei 2: astore_1 3: ldc #4 // String hello, 5: astore_2 6: aload_1 7: aload_2 8: invokedynamic #5, 0 // InvokeDynamic #0:print:(Ljava/lang/String;)Ltest/Print; 13: invokestatic #6 // Method print:(Ljava/lang/String;Ltest/Print;)V 16: aload_1 17: invokedynamic #7, 0 // InvokeDynamic #1:print:()Ltest/Print; 22: invokestatic #6 // Method print:(Ljava/lang/String;Ltest/Print;)V 25: return LineNumberTable: line 13: 0 line 14: 3 line 16: 6 line 18: 16 line 19: 25 private static void Lambda$main$1(java.lang.String); descriptor: (Ljava/lang/String;)V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=2, locals=1, args_size=1 0: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 7: return LineNumberTable: line 18: 0 private static void Lambda$main$0(java.lang.String, java.lang.String); descriptor: (Ljava/lang/String;Ljava/lang/String;)V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=3, locals=2, args_size=2 0: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #10 // class java/lang/StringBuilder 6: dup 7: invokespecial #11 // Method java/lang/StringBuilder."<init>":()V 10: aload_0 11: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: aload_1 15: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 18: invokevirtual #13 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 21: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 24: return LineNumberTable: line 16: 0 } SourceFile: "LambdaTest.java" InnerClasses: public static final #74= #73 of #77; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles BootstrapMethods: 0: #36 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:( Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite; Method arguments: #37 (Ljava/lang/String;)V #38 invokestatic test/LambdaTest.Lambda$main$0:(Ljava/lang/String;Ljava/lang/String;)V #37 (Ljava/lang/String;)V 1: #36 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:( Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite; Method arguments: #37 (Ljava/lang/String;)V #41 invokestatic test/LambdaTest.Lambda$main$1:(Ljava/lang/String;)V #37 (Ljava/lang/String;)V
這個 class 檔案展示了三個主要部分:
常數池
構造方法和 main、print、Lambdamain0、Lambdamain1方法
Lambda表示式生成的內部類。
重點看下main方法的實現:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 // 將字串常數"Chen Longfei"從常數池壓棧到運算元棧 0: ldc #3 // String Chen Longfei // 將棧頂參照型數值存入第二個本地變,即 String name = "Chen Longfei" 2: astore_1 // 將字串常數"hello,"從常數池壓棧到運算元棧 3: ldc #4 // String hello, // 將棧頂參照型數值存入第三個本地變數, 即 String prefix = "hello, " 5: astore_2 //將第二個參照型別本地變數推播至棧頂,即 name 6: aload_1 //將第三個參照型別本地變數推播至棧頂,即 prefix 7: aload_2 //通過invokedynamic指令建立Print介面的實匿名內部類,實現 (t) -> System.out.println(prefix + t) 8: invokedynamic #5, 0 // InvokeDynamic #0:print:(Ljava/lang/String;)Ltest/Print; //呼叫靜態方法print 13: invokestatic #6 // Method print:(Ljava/lang/String;Ltest/Print;)V //將第二個參照型別本地變數推播至棧頂,即 name 16: aload_1 //通過invokedynamic指令建立Print介面的匿名內部類,實現 (t) -> System.out.println(t) 17: invokedynamic #7, 0 // InvokeDynamic #1:print:()Ltest/Print; //呼叫靜態方法print 22: invokestatic #6 // Method print:(Ljava/lang/String;Ltest/Print;)V 25: return ……
兩個匿名內部類是通過BootstrapMethods方法建立的:
匿名內部類
InnerClasses: public static final #74= #73 of #77; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles BootstrapMethods: //呼叫靜態工廠LambdaMetafactory.metafactory建立匿名內部類1。實現了 (t) -> System.out.println(prefix + t) 0: #36 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:( Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite; Method arguments: #37 (Ljava/lang/String;)V //該類會呼叫靜態方法LambdaTest.Lambda$main$0 #38 invokestatic test/LambdaTest.Lambda$main$0:(Ljava/lang/String;Ljava/lang/String;)V #37 (Ljava/lang/String;)V //呼叫靜態工廠LambdaMetafactory.metafactory建立匿名內部類2,實現了 (t) -> System.out.println(t) 1: #36 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:( Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite; Method arguments: #37 (Ljava/lang/String;)V //該類會呼叫靜態方法LambdaTest.Lambda$main$1 #41 invokestatic test/LambdaTest.Lambda$main$1:(Ljava/lang/String;)V #37 (Ljava/lang/String;)V
可以在執行時加上-Djdk.internal.Lambda.dumpProxyClasses=%PATH%,加上這個引數後,執行時,會將生成的內部類class輸出到%PATH%路徑下。
javap -p -c 反編譯兩個檔案:
//print(name, (t) -> System.out.println(t))的範例
final class test.LambdaTest$$Lambda$1 implements test.Print { private test.LambdaTest$$Lambda$1(); //構造方法 Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>":()V 4: return //實現test.Print介面方法 public void print(java.lang.String); Code: 0: aload_1 //呼叫靜態方法LambdaTest.Lambda$1 1: invokestatic #18 // Method test/LambdaTest.Lambda$1:(Ljava/lang/String;)V 4: return } //print(name, (t) -> System.out.println(prefix + t))的範例 final class test.LambdaTest$$Lambda$2 implements test.Print { private final java.lang.String arg$1; private test.LambdaTest$$Lambda$2(java.lang.String); Code: 0: aload_0 1: invokespecial #13 // Method java/lang/Object."<init>":()V 4: aload_0 5: aload_1 //final變數arg$1由構造方法傳入 6: putfield #15 // Field arg$1:Ljava/lang/String; 9: return //該方法返回一個 LambdaTest$$Lambda$2範例 private static test.Print get$Lambda(java.lang.String); Code: 0: new #2 // class test/LambdaTest$$Lambda$2 3: dup 4: aload_0 5: invokespecial #19 // Method "<init>":(Ljava/lang/String;)V 8: areturn //實現test.Print介面方法 public void print(java.lang.String); Code: 0: aload_0 1: getfield #15 // Field arg$1:Ljava/lang/String; 4: aload_1 //呼叫靜態方法LambdaTest.Lambda$0 5: invokestatic #27 // Method test/LambdaTest.Lambda$0:(Ljava/lang/String;Ljava/lang/String;)V 8: return }
對比兩個範例,可以發現,由於表示式print(name, (t) -> System.out.println(prefix + t))參照了區域性變數prefix,LambdaTestKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2類 多了一個final引數:…Lambda$2參照了同一份變數,該變數雖然在程式碼層面獨立儲存於兩個類當中,但是在邏輯上具有一致性,所以匿名內部類中加上了final關鍵字,而外部類中雖然沒有為prefix顯式地新增final,但是在被Lambda表示式參照後,該變數就自動隱含了final語意(再次更改會報錯)。
通過上面的例子可以發現,Lambda表示式由虛擬機器器指令InvokeDynamic實現方法呼叫。
方法呼叫不等同於方法執行,方法呼叫階段的唯一任務就是確定被呼叫方法的版本(即確定具體呼叫那一個方法),不涉及方法內部具體執行。
方法呼叫不等同於方法執行,方法呼叫階段的唯一任務就是確定被呼叫方法的版本(即確定具體呼叫那一個方法),不涉及方法內部具體執行。
java虛擬機器器中提供了5條方法呼叫的位元組碼指令:
invokestatic:呼叫靜態方法
invokespecial:呼叫範例構造器方法、私有方法、父類別方法
invokevirtual:呼叫虛方法。
invokeinterface:呼叫介面方法,在執行時再確定一個實現該介面的物件
invokedynamic:執行時動態解析出呼叫的方法,然後去執行該方法。
invokeDynamic是 java 7 引入的一條新的虛擬機器器指令,這是自 1.0 以來第一次引入新的虛擬機器器指令。到了 java 8 這條指令才第一次在 java 應用,用在 Lambda 表示式中。invokeDynamic與其他invoke指令不同的是它允許由應用級的程式碼來決定方法解析。
根據JVM規範的規定,invokeDynamic的操作碼是186(0xBA),格式是:
invokedynamic indexbyte1 indexbyte2 0 0
invokeDynamic指令有四個運算元,前兩個運算元構成一個索引[ (indexbyte1 << 8) | indexbyte2 ],指向類的常數池,後兩個運算元保留,必須是0。
檢視上例中LambdaTest類的反編譯結果,第一處Lambda表示式
print(name, (t) -> System.out.println(t));
對應的指令為:
17: invokedynamic #7, 0 // InvokeDynamic #1:print:()Ltest/Print;
常數池中#7對應的常數為:
#7 = InvokeDynamic #1:#42 // #1:print:()Ltest/Print;
其型別為CONSTANT_InvokeDynamic_info,CONSTANT_InvokeDynamic_info結構是Java7新引入class檔案的,其用途就是給invokeDynamic指令指定啟動方法(bootstrap method)、呼叫點call site()等資訊, 實際上是個 MethodHandle(方法控制程式碼)物件。
#1代表BootstrapMethods表中的索引,即
BootstrapMethods:
//第一個
0: #36 ……//第二個
1: #36 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(
Ljava/lang/invoke/MethodHandles$Lookup;
Ljava/lang/String;
Ljava/lang/invoke/MethodType;
Ljava/lang/invoke/MethodType;
Ljava/lang/invoke/MethodHandle;
Ljava/lang/invoke/MethodType;)
Ljava/lang/invoke/CallSite;
Method arguments:# 37 (Ljava/lang/String;)V
# 41 invokestatic test/LambdaTest.Lambda$main$1:(Ljava/lang/String;)V
# 37 (Ljava/lang/String;)V
也就是說,最終呼叫的是java.lang.invoke.LambdaMetafactory類的靜態方法metafactory()。
為了更深入的瞭解invokeDynamic,先來看幾個術語:
dynamic call site
程式中出現Lambda的地方都被稱作dynamic call site,CallSite 就是一個 MethodHandle(方法控制程式碼)的 holder。方法控制程式碼指向一個呼叫點真正執行的方法。
bootstrap method
java裡對所有Lambda的有統一的bootstrap method(LambdaMetafactory.metafactory),bootstrap執行期動態生成了匿名類,將其與CallSite繫結,得到了一個獲取匿名類範例的call site object
call site object
call site object持有MethodHandle的參照作為它的target,它是bootstrap method方法成功呼叫後的結果,將會與 dynamic call site永久繫結。call site object的target會被JVM執行,就如同執行一條invokevirtual指令,其所需的引數也會被壓入operand stack。最後會得一個實現了functional interface的物件。
InvokeDynamic 首先需要生成一個 CallSite(呼叫點物件),CallSite 是由 bootstrap method 返回,也就是調LambdaMetafactory.metafactory方法。
public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) throws LambdaConversionException { AbstractValidatingLambdaMetafactory mf; mf = new InnerClassLambdaMetafactory(caller, invokedType, invokedName, samMethodType, implMethod, instantiatedMethodType, false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY); mf.validateMetafactoryArgs(); return mf.buildCallSite(); }
前三個引數是固定的,由VM自動壓棧:
MethodHandles.Lookup caller代表InvokeDynamic 指令所在的類的上下文(在上例中就是LambdaTest),可以通過 Lookup#lookupClass()獲取這個類
String invokedName表示要實現的方法名(在上例中就是Print介面的方法名“print”)
MethodType invokedType call site object所持有的MethodHandle需要的引數和返回型別(signature)
接下來就是附加引數,這些引數是靈活的,由Bootstrap methods表提供:
MethodType samMethodType表示要實現functional interface裡面抽象方法的型別
MethodHandle implMethod表示編譯器給生成的 desugar 方法,是一個 MethodHandle
MethodType instantiatedMethodType即執行時的型別,因為方法定義可能是泛型,傳入時可能是具體型別String之類的,要做型別校驗強轉等等
LambdaMetafactory.metafactory 方法會建立一個VM Anonymous Class,這個類是通過 ASM 編織位元組碼在記憶體中生成的,然後直接通過 UNSAFE 直接載入而不會寫到檔案裡。VM Anonymous Class 是真正意義上的匿名類,不需要 ClassLoader 載入,沒有類名,當然也沒其他許可權管理等操作,這意味著效率更高(不必要的鎖操作)、GC 更方便(沒有 ClassLoader)。
要讓invokedynamic正常執行,一個核心的概念就是方法控制程式碼(method handle)。它代表了一個可以從invokedynamic呼叫點進行呼叫的方法。每個invokedynamic指令都會與一個特定的方法關聯(也就是bootstrap method或BSM)。當編譯器遇到invokedynamic指令的時候,BSM會被呼叫,會返回一個包含了方法控制程式碼的物件,這個物件表明了呼叫點要實際執行哪個方法。
Java 7 API中加入了java.lang.invoke.MethodHandle(及其子類),通過它們來代表invokedynamic指向的方法。 一個Java方法可以視為由四個基本內容所構成:
名稱
簽名(包含返回型別)
定義它的類
實現方法的位元組碼
這意味著如果要參照某個方法,我們需要有一種有效的方式來表示方法簽名(而不是反射中強制使用的令人討厭的Class<?>[] hack方式)。
方法控制程式碼首先需要的一個表達方法簽名的方式,以便於查詢。在Java 7引入的Method Handles API中,這個角色是由java.lang.invoke.MethodType類來完成的,它使用一個不可變的範例來代表簽名。要獲取MethodType,我們可以使用methodType()工廠方法。這是一個引數可變的方法,以class物件作為引數。 第一個引數所使用的class物件,對應著簽名的返回型別;剩餘引數中所使用的class物件,對應著簽名中方法引數的型別。例如:
//toString()的簽名 MethodType mtToString = MethodType.methodType(String.class); // setter方法的簽名 MethodType mtSetter = MethodType.methodType(void.class, Object.class); // Comparator中compare()方法的簽名 MethodType mtStringComparator = MethodType.methodType(int.class, String.class, String.class);
現在我們就可以使用MethodType,再組合方法名稱以及定義方法的類來查詢方法控制程式碼。要實現這一點,我們需要呼叫靜態的MethodHandles.lookup()方法。這樣的話,會給我們一個“查詢上下文(lookup context)”,這個上下文基於當前正在執行的方法(也就是呼叫lookup()的方法)的存取許可權。
查詢上下文物件有一些以“find”開頭的方法,例如,findVirtual()、findConstructor()、findStatic()等。這些方法將會返回實際的方法控制程式碼,需要注意的是,只有在建立查詢上下文的方法能夠存取(呼叫)被請求方法的情況下,才會返回控制程式碼。這與反射不同,我們沒有辦法繞過存取控制。換句話說,方法控制程式碼中並沒有與setAccessible()對應的方法。例如
public MethodHandle getToStringMH() { MethodHandle mh = null; MethodType mt = MethodType.methodType(String.class); MethodHandles.Lookup lk = MethodHandles.lookup(); try { mh = lk.findVirtual(getClass(), "toString", mt); } catch (NoSuchMethodException | IllegalAccessException mhx) { throw (AssertionError) new AssertionError().initCause(mhx); } return mh; }
MethodHandle中有兩個方法能夠觸發對方法控制程式碼的呼叫,那就是invoke()和invokeExact()。這兩個方法都是以接收者(receiver)和呼叫變數作為引數,所以它們的簽名為:
public final Object invoke(Object... args) throws Throwable; public final Object invokeExact(Object... args) throws Throwable;
兩者的區別在於,invokeExact()在呼叫方法控制程式碼時會試圖嚴格地直接匹配所提供的變數。而invoke()與之不同,在需要的時候,invoke()能夠稍微調整一下方法的變數。invoke()會執行一個asType()轉換,它會根據如下的這組規則來進行變數的轉換:
如果需要的話,原始型別會進行裝箱操作
如果需要的話,裝箱後的原始型別會進行拆箱操作
如果必要的話,原始型別會進行擴充套件
void返回型別會轉換為0(對於返回原始型別的情況),而對於預期得到參照型別的返回值的地方,將會轉換為null
null值會被視為正確的,不管靜態型別是什麼都可以進行傳遞
接下來,我們看一下考慮上述規則的簡單呼叫樣例:
Object rcvr = "a"; try { MethodType mt = MethodType.methodType(int.class); MethodHandles.Lookup l = MethodHandles.lookup(); MethodHandle mh = l.findVirtual(rcvr.getClass(), "hashCode", mt); int ret; try { ret = (int) mh.invoke(rcvr); System.out.println(ret); } catch (Throwable t) { t.printStackTrace(); } } catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); }
上面的程式碼呼叫了Object的hashcode()方法,看到這裡,你肯定會說這不就是 Java 的反射嗎?
確實,MethodHandl和 Reflection實現的功能有太多相似的地方,都是執行時解析方法呼叫,理解方法控制程式碼的一種方式就是將其視為以安全、現代的方式來實現反射的核心功能,在這個過程會盡可能地保證型別的安全。 但是,究其本質,兩者之間還是有區別的: Reflection中的java.lang.reflect.Method物件遠比MethodHandl機制中的java.lang.invoke.MethodHandle`物件所包含的資訊來得多。前者是方法在Java一端的全面映像,包含了方法的簽名、描述符以及方法屬性表中各種屬性的Java端表示方式,還包含有執行許可權等的執行期資訊。而後者僅僅包含著與執行該方法相關的資訊。用開發人員通俗的話來講,Reflection是重量級,而MethodHandle是輕量級。
從效能角度上說,MethodHandle 要比反射快很多,因為存取檢查在建立的時候就已經完成了,而不是像反射一樣等到執行時候才檢查
Reflection是在模擬Java程式碼層次的方法呼叫,而MethodHandle是在模擬位元組碼層次的方法呼叫。 MethodHandle 是結合 invokedynamic 指令一起為動態語言服務的,也就是說MethodHandle (更準確的來說是其設計理念)是服務於所有執行在JVM之上的語言,而 Relection 則只是適用 Java 語言本身。
到此這篇關於Java Lambda表示式範例解析原理的文章就介紹到這了,更多相關Java Lambda內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45