Java Lambda表達(dá)式實(shí)例解析原理
1、實(shí)例解析
先從一個(gè)例子開始:
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表達(dá)式的函數(shù)體中引用了外部變量‘prefix' print(name, (t) -> System.out.println(prefix + t)); } } @FunctionalInterface interface Print { void print(String name); }
例子很簡(jiǎn)單,定義了一個(gè)函數(shù)式接口Print ,main方法中有兩處代碼以Lambda表達(dá)式的方式實(shí)現(xiàn)了print接口,分別打印出不帶前綴與帶前綴的名字。
運(yùn)行程序,打印結(jié)果如下:
Chen Longfei
hello, Chen Longfei
而(t) -> System.out.println(t)與(t) -> System.out.println(prefix + t))之類的Lambda表達(dá)式到底是怎樣被編譯與調(diào)用的呢?
我們知道,編譯器編譯Java代碼時(shí)經(jīng)常在背地里“搞鬼”比如類的全限定名的補(bǔ)全,泛型的類型推斷等,編譯器耍的這些小聰明可以幫助我們寫出更優(yōu)雅、簡(jiǎn)潔、高效的代碼。鑒于編譯器的一貫作風(fēng),我們有理由懷疑,新穎而另類的Lambda表達(dá)式在編譯時(shí)很可能會(huì)被改造過(guò)了。
下面通過(guò)javap反編譯class文件一探究竟。 javap是jdk自帶的一個(gè)字節(jié)碼查看工具及反編譯工具: 用法: javap 其中, 可能的選項(xiàng)包括:
-help --help -? 輸出此用法消息
-version 版本信息
-v -verbose 輸出附加信息
-l 輸出行號(hào)和本地變量表
-public 僅顯示公共類和成員
-protected 顯示受保護(hù)的/公共類和成員
-package 顯示程序包/受保護(hù)的/公共類
和成員 (默認(rèn))
-p -private 顯示所有類和成員
-c 對(duì)代碼進(jìn)行反匯編
-s 輸出內(nèi)部類型簽名
-sysinfo 顯示正在處理的類的
系統(tǒng)信息 (路徑, 大小, 日期, MD5 散列)
-constants 顯示最終常量
-classpath <path> 指定查找用戶類文件的位置
-cp <path> 指定查找用戶類文件的位置
-bootclasspath <path> 覆蓋引導(dǎo)類文件的位置
結(jié)果如下:
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); }
可見(jiàn),編譯器對(duì)Print接口的改造比較小,只是為print方法添加了public abstract關(guān)鍵字,而對(duì)LambdaTest的變化就比較大了,添加了兩個(gè)靜態(tài)方法:
private static void Lambda$main$1(java.lang.String); private static void Lambda$main$0(java.lang.String, java.lang.String);
到底有什么關(guān)聯(lián)呢?使用javap -p -v -c LambdaTest.class查看更加詳細(xì)的反編譯結(jié)果:
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
這個(gè) class 文件展示了三個(gè)主要部分:
常量池
構(gòu)造方法和 main、print、Lambdamain0、Lambdamain1方法
Lambda表達(dá)式生成的內(nèi)部類。
重點(diǎn)看下main方法的實(shí)現(xiàn):
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"從常量池壓棧到操作數(shù)棧 0: ldc #3 // String Chen Longfei // 將棧頂引用型數(shù)值存入第二個(gè)本地變,即 String name = "Chen Longfei" 2: astore_1 // 將字符串常量"hello,"從常量池壓棧到操作數(shù)棧 3: ldc #4 // String hello, // 將棧頂引用型數(shù)值存入第三個(gè)本地變量, 即 String prefix = "hello, " 5: astore_2 //將第二個(gè)引用類型本地變量推送至棧頂,即 name 6: aload_1 //將第三個(gè)引用類型本地變量推送至棧頂,即 prefix 7: aload_2 //通過(guò)invokedynamic指令創(chuàng)建Print接口的實(shí)匿名內(nèi)部類,實(shí)現(xiàn) (t) -> System.out.println(prefix + t) 8: invokedynamic #5, 0 // InvokeDynamic #0:print:(Ljava/lang/String;)Ltest/Print; //調(diào)用靜態(tài)方法print 13: invokestatic #6 // Method print:(Ljava/lang/String;Ltest/Print;)V //將第二個(gè)引用類型本地變量推送至棧頂,即 name 16: aload_1 //通過(guò)invokedynamic指令創(chuàng)建Print接口的匿名內(nèi)部類,實(shí)現(xiàn) (t) -> System.out.println(t) 17: invokedynamic #7, 0 // InvokeDynamic #1:print:()Ltest/Print; //調(diào)用靜態(tài)方法print 22: invokestatic #6 // Method print:(Ljava/lang/String;Ltest/Print;)V 25: return ……
兩個(gè)匿名內(nèi)部類是通過(guò)BootstrapMethods方法創(chuàng)建的:
匿名內(nèi)部類
InnerClasses: public static final #74= #73 of #77; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles BootstrapMethods: //調(diào)用靜態(tài)工廠LambdaMetafactory.metafactory創(chuàng)建匿名內(nèi)部類1。實(shí)現(xiàn)了 (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 //該類會(huì)調(diào)用靜態(tài)方法LambdaTest.Lambda$main$0 #38 invokestatic test/LambdaTest.Lambda$main$0:(Ljava/lang/String;Ljava/lang/String;)V #37 (Ljava/lang/String;)V //調(diào)用靜態(tài)工廠LambdaMetafactory.metafactory創(chuàng)建匿名內(nèi)部類2,實(shí)現(xiàn)了 (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 //該類會(huì)調(diào)用靜態(tài)方法LambdaTest.Lambda$main$1 #41 invokestatic test/LambdaTest.Lambda$main$1:(Ljava/lang/String;)V #37 (Ljava/lang/String;)V
可以在運(yùn)行時(shí)加上-Djdk.internal.Lambda.dumpProxyClasses=%PATH%,加上這個(gè)參數(shù)后,運(yùn)行時(shí),會(huì)將生成的內(nèi)部類class輸出到%PATH%路徑下。
javap -p -c 反編譯兩個(gè)文件:
//print(name, (t) -> System.out.println(t))的實(shí)例
final class test.LambdaTest$$Lambda$1 implements test.Print { private test.LambdaTest$$Lambda$1(); //構(gòu)造方法 Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>":()V 4: return //實(shí)現(xiàn)test.Print接口方法 public void print(java.lang.String); Code: 0: aload_1 //調(diào)用靜態(tài)方法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))的實(shí)例 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由構(gòu)造方法傳入 6: putfield #15 // Field arg$1:Ljava/lang/String; 9: return //該方法返回一個(gè) LambdaTest$$Lambda$2實(shí)例 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 //實(shí)現(xiàn)test.Print接口方法 public void print(java.lang.String); Code: 0: aload_0 1: getfield #15 // Field arg$1:Ljava/lang/String; 4: aload_1 //調(diào)用靜態(tài)方法LambdaTest.Lambda$0 5: invokestatic #27 // Method test/LambdaTest.Lambda$0:(Ljava/lang/String;Ljava/lang/String;)V 8: return }
對(duì)比兩個(gè)實(shí)例,可以發(fā)現(xiàn),由于表達(dá)式print(name, (t) -> System.out.println(prefix + t))引用了局部變量prefix,LambdaTestKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$?2類 多了一個(gè)final參數(shù):…Lambda$2引用了同一份變量,該變量雖然在代碼層面獨(dú)立存儲(chǔ)于兩個(gè)類當(dāng)中,但是在邏輯上具有一致性,所以匿名內(nèi)部類中加上了final關(guān)鍵字,而外部類中雖然沒(méi)有為prefix顯式地添加final,但是在被Lambda表達(dá)式引用后,該變量就自動(dòng)隱含了final語(yǔ)意(再次更改會(huì)報(bào)錯(cuò))。
2、InvokeDynamic
通過(guò)上面的例子可以發(fā)現(xiàn),Lambda表達(dá)式由虛擬機(jī)指令I(lǐng)nvokeDynamic實(shí)現(xiàn)方法調(diào)用。
2.1 方法調(diào)用
方法調(diào)用不等同于方法執(zhí)行,方法調(diào)用階段的唯一任務(wù)就是確定被調(diào)用方法的版本(即確定具體調(diào)用那一個(gè)方法),不涉及方法內(nèi)部具體運(yùn)行。
方法調(diào)用不等同于方法執(zhí)行,方法調(diào)用階段的唯一任務(wù)就是確定被調(diào)用方法的版本(即確定具體調(diào)用那一個(gè)方法),不涉及方法內(nèi)部具體運(yùn)行。
java虛擬機(jī)中提供了5條方法調(diào)用的字節(jié)碼指令:
invokestatic:調(diào)用靜態(tài)方法
invokespecial:調(diào)用實(shí)例構(gòu)造器方法、私有方法、父類方法
invokevirtual:調(diào)用虛方法。
invokeinterface:調(diào)用接口方法,在運(yùn)行時(shí)再確定一個(gè)實(shí)現(xiàn)該接口的對(duì)象
invokedynamic:運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用的方法,然后去執(zhí)行該方法。
invokeDynamic是 java 7 引入的一條新的虛擬機(jī)指令,這是自 1.0 以來(lái)第一次引入新的虛擬機(jī)指令。到了 java 8 這條指令才第一次在 java 應(yīng)用,用在 Lambda 表達(dá)式中。invokeDynamic與其他invoke指令不同的是它允許由應(yīng)用級(jí)的代碼來(lái)決定方法解析。
2.2 指令規(guī)范
根據(jù)JVM規(guī)范的規(guī)定,invokeDynamic的操作碼是186(0xBA),格式是:
invokedynamic indexbyte1 indexbyte2 0 0
invokeDynamic指令有四個(gè)操作數(shù),前兩個(gè)操作數(shù)構(gòu)成一個(gè)索引[ (indexbyte1 << 8) | indexbyte2 ],指向類的常量池,后兩個(gè)操作數(shù)保留,必須是0。
查看上例中LambdaTest類的反編譯結(jié)果,第一處Lambda表達(dá)式
print(name, (t) -> System.out.println(t));
對(duì)應(yīng)的指令為:
17: invokedynamic #7, 0 // InvokeDynamic #1:print:()Ltest/Print;
常量池中#7對(duì)應(yīng)的常量為:
#7 = InvokeDynamic #1:#42 // #1:print:()Ltest/Print;
其類型為CONSTANT_InvokeDynamic_info,CONSTANT_InvokeDynamic_info結(jié)構(gòu)是Java7新引入class文件的,其用途就是給invokeDynamic指令指定啟動(dòng)方法(bootstrap method)、調(diào)用點(diǎn)call site()等信息, 實(shí)際上是個(gè) MethodHandle(方法句柄)對(duì)象。
#1代表BootstrapMethods表中的索引,即
BootstrapMethods:
//第一個(gè)
0: #36 ……//第二個(gè)
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
也就是說(shuō),最終調(diào)用的是java.lang.invoke.LambdaMetafactory類的靜態(tài)方法metafactory()。
2.3 執(zhí)行過(guò)程
為了更深入的了解invokeDynamic,先來(lái)看幾個(gè)術(shù)語(yǔ):
dynamic call site
程序中出現(xiàn)Lambda的地方都被稱作dynamic call site,CallSite 就是一個(gè) MethodHandle(方法句柄)的 holder。方法句柄指向一個(gè)調(diào)用點(diǎn)真正執(zhí)行的方法。
bootstrap method
java里對(duì)所有Lambda的有統(tǒng)一的bootstrap method(LambdaMetafactory.metafactory),bootstrap運(yùn)行期動(dòng)態(tài)生成了匿名類,將其與CallSite綁定,得到了一個(gè)獲取匿名類實(shí)例的call site object
call site object
call site object持有MethodHandle的引用作為它的target,它是bootstrap method方法成功調(diào)用后的結(jié)果,將會(huì)與 dynamic call site永久綁定。call site object的target會(huì)被JVM執(zhí)行,就如同執(zhí)行一條invokevirtual指令,其所需的參數(shù)也會(huì)被壓入operand stack。最后會(huì)得一個(gè)實(shí)現(xiàn)了functional interface的對(duì)象。
InvokeDynamic 首先需要生成一個(gè) CallSite(調(diào)用點(diǎn)對(duì)象),CallSite 是由 bootstrap method 返回,也就是調(diào)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(); }
前三個(gè)參數(shù)是固定的,由VM自動(dòng)壓棧:
MethodHandles.Lookup caller代表InvokeDynamic 指令所在的類的上下文(在上例中就是LambdaTest),可以通過(guò) Lookup#lookupClass()獲取這個(gè)類
String invokedName表示要實(shí)現(xiàn)的方法名(在上例中就是Print接口的方法名“print”)
MethodType invokedType call site object所持有的MethodHandle需要的參數(shù)和返回類型(signature)
接下來(lái)就是附加參數(shù),這些參數(shù)是靈活的,由Bootstrap methods表提供:
MethodType samMethodType表示要實(shí)現(xiàn)functional interface里面抽象方法的類型
MethodHandle implMethod表示編譯器給生成的 desugar 方法,是一個(gè) MethodHandle
MethodType instantiatedMethodType即運(yùn)行時(shí)的類型,因?yàn)榉椒ǘx可能是泛型,傳入時(shí)可能是具體類型String之類的,要做類型校驗(yàn)強(qiáng)轉(zhuǎn)等等
LambdaMetafactory.metafactory 方法會(huì)創(chuàng)建一個(gè)VM Anonymous Class,這個(gè)類是通過(guò) ASM 編織字節(jié)碼在內(nèi)存中生成的,然后直接通過(guò) UNSAFE 直接加載而不會(huì)寫到文件里。VM Anonymous Class 是真正意義上的匿名類,不需要 ClassLoader 加載,沒(méi)有類名,當(dāng)然也沒(méi)其他權(quán)限管理等操作,這意味著效率更高(不必要的鎖操作)、GC 更方便(沒(méi)有 ClassLoader)。
2.4 MethodHandle
要讓invokedynamic正常運(yùn)行,一個(gè)核心的概念就是方法句柄(method handle)。它代表了一個(gè)可以從invokedynamic調(diào)用點(diǎn)進(jìn)行調(diào)用的方法。每個(gè)invokedynamic指令都會(huì)與一個(gè)特定的方法關(guān)聯(lián)(也就是bootstrap method或BSM)。當(dāng)編譯器遇到invokedynamic指令的時(shí)候,BSM會(huì)被調(diào)用,會(huì)返回一個(gè)包含了方法句柄的對(duì)象,這個(gè)對(duì)象表明了調(diào)用點(diǎn)要實(shí)際執(zhí)行哪個(gè)方法。
Java 7 API中加入了java.lang.invoke.MethodHandle(及其子類),通過(guò)它們來(lái)代表invokedynamic指向的方法。 一個(gè)Java方法可以視為由四個(gè)基本內(nèi)容所構(gòu)成:
名稱
簽名(包含返回類型)
定義它的類
實(shí)現(xiàn)方法的字節(jié)碼
這意味著如果要引用某個(gè)方法,我們需要有一種有效的方式來(lái)表示方法簽名(而不是反射中強(qiáng)制使用的令人討厭的Class<?>[] hack方式)。
方法句柄首先需要的一個(gè)表達(dá)方法簽名的方式,以便于查找。在Java 7引入的Method Handles API中,這個(gè)角色是由java.lang.invoke.MethodType類來(lái)完成的,它使用一個(gè)不可變的實(shí)例來(lái)代表簽名。要獲取MethodType,我們可以使用methodType()工廠方法。這是一個(gè)參數(shù)可變的方法,以class對(duì)象作為參數(shù)。 第一個(gè)參數(shù)所使用的class對(duì)象,對(duì)應(yīng)著簽名的返回類型;剩余參數(shù)中所使用的class對(duì)象,對(duì)應(yīng)著簽名中方法參數(shù)的類型。例如:
//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);
現(xiàn)在我們就可以使用MethodType,再組合方法名稱以及定義方法的類來(lái)查找方法句柄。要實(shí)現(xiàn)這一點(diǎn),我們需要調(diào)用靜態(tài)的MethodHandles.lookup()方法。這樣的話,會(huì)給我們一個(gè)“查找上下文(lookup context)”,這個(gè)上下文基于當(dāng)前正在執(zhí)行的方法(也就是調(diào)用lookup()的方法)的訪問(wèn)權(quán)限。
查找上下文對(duì)象有一些以“find”開頭的方法,例如,findVirtual()、findConstructor()、findStatic()等。這些方法將會(huì)返回實(shí)際的方法句柄,需要注意的是,只有在創(chuàng)建查找上下文的方法能夠訪問(wèn)(調(diào)用)被請(qǐng)求方法的情況下,才會(huì)返回句柄。這與反射不同,我們沒(méi)有辦法繞過(guò)訪問(wèn)控制。換句話說(shuō),方法句柄中并沒(méi)有與setAccessible()對(duì)應(yīng)的方法。例如
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中有兩個(gè)方法能夠觸發(fā)對(duì)方法句柄的調(diào)用,那就是invoke()和invokeExact()。這兩個(gè)方法都是以接收者(receiver)和調(diào)用變量作為參數(shù),所以它們的簽名為:
public final Object invoke(Object... args) throws Throwable; public final Object invokeExact(Object... args) throws Throwable;
兩者的區(qū)別在于,invokeExact()在調(diào)用方法句柄時(shí)會(huì)試圖嚴(yán)格地直接匹配所提供的變量。而invoke()與之不同,在需要的時(shí)候,invoke()能夠稍微調(diào)整一下方法的變量。invoke()會(huì)執(zhí)行一個(gè)asType()轉(zhuǎn)換,它會(huì)根據(jù)如下的這組規(guī)則來(lái)進(jìn)行變量的轉(zhuǎn)換:
如果需要的話,原始類型會(huì)進(jìn)行裝箱操作
如果需要的話,裝箱后的原始類型會(huì)進(jìn)行拆箱操作
如果必要的話,原始類型會(huì)進(jìn)行擴(kuò)展
void返回類型會(huì)轉(zhuǎn)換為0(對(duì)于返回原始類型的情況),而對(duì)于預(yù)期得到引用類型的返回值的地方,將會(huì)轉(zhuǎn)換為null
null值會(huì)被視為正確的,不管靜態(tài)類型是什么都可以進(jìn)行傳遞
接下來(lái),我們看一下考慮上述規(guī)則的簡(jiǎn)單調(diào)用樣例:
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(); }
上面的代碼調(diào)用了Object的hashcode()方法,看到這里,你肯定會(huì)說(shuō)這不就是 Java 的反射嗎?
確實(shí),MethodHandl和 Reflection實(shí)現(xiàn)的功能有太多相似的地方,都是運(yùn)行時(shí)解析方法調(diào)用,理解方法句柄的一種方式就是將其視為以安全、現(xiàn)代的方式來(lái)實(shí)現(xiàn)反射的核心功能,在這個(gè)過(guò)程會(huì)盡可能地保證類型的安全。 但是,究其本質(zhì),兩者之間還是有區(qū)別的: Reflection中的java.lang.reflect.Method對(duì)象遠(yuǎn)比MethodHandl機(jī)制中的java.lang.invoke.MethodHandle`對(duì)象所包含的信息來(lái)得多。前者是方法在Java一端的全面映像,包含了方法的簽名、描述符以及方法屬性表中各種屬性的Java端表示方式,還包含有執(zhí)行權(quán)限等的運(yùn)行期信息。而后者僅僅包含著與執(zhí)行該方法相關(guān)的信息。用開發(fā)人員通俗的話來(lái)講,Reflection是重量級(jí),而MethodHandle是輕量級(jí)。
從性能角度上說(shuō),MethodHandle 要比反射快很多,因?yàn)樵L問(wèn)檢查在創(chuàng)建的時(shí)候就已經(jīng)完成了,而不是像反射一樣等到運(yùn)行時(shí)候才檢查
Reflection是在模擬Java代碼層次的方法調(diào)用,而MethodHandle是在模擬字節(jié)碼層次的方法調(diào)用。 MethodHandle 是結(jié)合 invokedynamic 指令一起為動(dòng)態(tài)語(yǔ)言服務(wù)的,也就是說(shuō)MethodHandle (更準(zhǔn)確的來(lái)說(shuō)是其設(shè)計(jì)理念)是服務(wù)于所有運(yùn)行在JVM之上的語(yǔ)言,而 Relection 則只是適用 Java 語(yǔ)言本身。
到此這篇關(guān)于Java Lambda表達(dá)式實(shí)例解析原理的文章就介紹到這了,更多相關(guān)Java Lambda內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何將char類型的數(shù)字字符轉(zhuǎn)換成int類型問(wèn)題
這篇文章主要介紹了如何將char類型的數(shù)字字符轉(zhuǎn)換成int類型問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12SpringBoot?整合RabbitMq?自定義消息監(jiān)聽容器來(lái)實(shí)現(xiàn)消息批量處理
Spring Boot中提供了默認(rèn)的監(jiān)聽器容器,但是有時(shí)候我們需要自定義監(jiān)聽器容器,來(lái)滿足一些特殊的需求,比如批量獲取數(shù)據(jù),這篇文章主要介紹了SpringBoot?整合RabbitMq?自定義消息監(jiān)聽容器來(lái)實(shí)現(xiàn)消息批量處理,需要的朋友可以參考下2023-04-04Spring 定時(shí)任務(wù)@Scheduled 注解中的 Cron 表達(dá)式詳解
Cron 表達(dá)式是一種用于定義定時(shí)任務(wù)觸發(fā)時(shí)間的字符串表示形式,它由七個(gè)字段組成,分別表示秒、分鐘、小時(shí)、日期、月份、星期和年份,這篇文章主要介紹了Spring 定時(shí)任務(wù)@Scheduled 注解中的 Cron 表達(dá)式,需要的朋友可以參考下2023-07-07深入理解JVM之Java對(duì)象的創(chuàng)建、內(nèi)存布局、訪問(wèn)定位詳解
這篇文章主要介紹了深入理解JVM之Java對(duì)象的創(chuàng)建、內(nèi)存布局、訪問(wèn)定位,結(jié)合實(shí)例形式詳細(xì)分析了Java對(duì)象的創(chuàng)建、內(nèi)存布局、訪問(wèn)定位相關(guān)概念、原理、操作技巧與注意事項(xiàng),需要的朋友可以參考下2019-09-09