Java 8 動態(tài)類型語言Lambda表達(dá)式實現(xiàn)原理解析
Java 8支持動態(tài)語言,看到了很酷的Lambda表達(dá)式,對一直以靜態(tài)類型語言自居的Java,讓人看到了Java虛擬機可以支持動態(tài)語言的目標(biāo)。
import java.util.function.Consumer; public class Lambda { public static void main(String[] args) { Consumer<String> c = s -> System.out.println(s); c.accept("hello lambda!"); } }
剛看到這個表達(dá)式,感覺java的處理方式是屬于內(nèi)部匿名類的方式
public class Lambda { static { System.setProperty("jdk.internal.lambda.dumpProxyClasses", "."); } public static void main(String[] args) { Consumer<String> c = new Consumer<String>(){ @Override public void accept(String s) { System.out.println(s); } }; c.accept("hello lambda"); } }
編譯的結(jié)果應(yīng)該是Lambda.class , Lambda$1.class 猜測在支持動態(tài)語言java換湯不換藥,在最后編譯的時候生成我們常見的方式。
但是結(jié)果不是這樣的,只是產(chǎn)生了一個Lambda.class
反編譯吧,來看看真相是什么?
javap -v -p Lambda.class
注意 -p 這個參數(shù) -p 參數(shù)會顯示所有的方法,而不帶默認(rèn)是不會反編譯private 的方法的
public Lambda(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #21 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LLambda; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: invokedynamic #30, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer; 5: astore_1 6: aload_1 7: ldc #31 // String hello lambda 9: invokeinterface #33, 2 // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V 14: return LineNumberTable: line 8: 0 line 9: 6 line 10: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 args [Ljava/lang/String; 6 9 1 c Ljava/util/function/Consumer; LocalVariableTypeTable: Start Length Slot Name Signature 6 9 1 c Ljava/util/function/Consumer<Ljava/lang/String;>; private static void lambda$0(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 #46 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: invokevirtual #50 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 7: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 8 0 s Ljava/lang/String; } SourceFile: "Lambda.java" BootstrapMethods: 0: #66 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: #67 (Ljava/lang/Object;)V #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V #71 (Ljava/lang/String;)V InnerClasses: public static final #77= #73 of #75; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
在這里我們發(fā)現(xiàn)了幾個與我們常見的java不太一樣的地方,由于常量定義太多了,文章中就不貼出了
1. Invokedynamic 指令
Java的調(diào)用函數(shù)的四大指令(invokevirtual、invokespecial、invokestatic、invokeinterface),通常方法的符號引用在靜態(tài)類型語言編譯時就能產(chǎn)生,而動態(tài)類型語言只有在運行期才能確定接收者類型,改變四大指令的語意對java的版本有很大的影響,所以在JSR 292 《Supporting Dynamically Typed Languages on the Java Platform》添加了一個新的指令
Invokedynamic
0: invokedynamic #30, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
#30 是代表常量#30 也就是后面的注釋InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
0 是占位符號,目前無用
2. BootstrapMethods
每一個invokedynamic指令的實例叫做一個動態(tài)調(diào)用點(dynamic call site), 動態(tài)調(diào)用點最開始是未鏈接狀態(tài)(unlinked:表示還未指定該調(diào)用點要調(diào)用的方法), 動態(tài)調(diào)用點依靠引導(dǎo)方法來鏈接到具體的方法. 引導(dǎo)方法是由編譯器生成, 在運行期當(dāng)JVM第一次遇到invokedynamic指令時, 會調(diào)用引導(dǎo)方法來將invokedynamic指令所指定的名字(方法名,方法簽名)和具體的執(zhí)行代碼(目標(biāo)方法)鏈接起來, 引導(dǎo)方法的返回值永久的決定了調(diào)用點的行為.引導(dǎo)方法的返回值類型是java.lang.invoke.CallSite, 一個invokedynamic指令關(guān)聯(lián)一個CallSite, 將所有的調(diào)用委托到CallSite當(dāng)前的target(MethodHandle)
InvokeDynamic #0 就是BootstrapMethods表示#0的位置
0: #66 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: #67 (Ljava/lang/Object;)V #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V #71 (Ljava/lang/String;)V
我們看到調(diào)用了LambdaMetaFactory.metafactory 的方法
參數(shù):
LambdaMetafactory.metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)有六個參數(shù), 按順序描述如下
1. MethodHandles.Lookup caller : 代表查找上下文與調(diào)用者的訪問權(quán)限, 使用invokedynamic指令時, JVM會自動自動填充這個參數(shù)
2. String invokedName : 要實現(xiàn)的方法的名字, 使用invokedynamic時, JVM自動幫我們填充(填充內(nèi)容來自常量池InvokeDynamic.NameAndType.Name), 在這里JVM為我們填充為 "apply", 即Consumer.accept方法名.
3. MethodType invokedType : 調(diào)用點期望的方法參數(shù)的類型和返回值的類型(方法signature). 使用invokedynamic指令時, JVM會自動自動填充這個參數(shù)(填充內(nèi)容來自常量池InvokeDynamic.NameAndType.Type), 在這里參數(shù)為String, 返回值類型為Consumer, 表示這個調(diào)用點的目標(biāo)方法的參數(shù)為String, 然后invokedynamic執(zhí)行完后會返回一個即Consumer實例.
4. MethodType samMethodType : 函數(shù)對象將要實現(xiàn)的接口方法類型, 這里運行時, 值為 (Object)Object 即 Consumer.accept方法的類型(泛型信息被擦除).#67 (Ljava/lang/Object;)V
5. MethodHandle implMethod : 一個直接方法句柄(DirectMethodHandle), 描述在調(diào)用時將被執(zhí)行的具體實現(xiàn)方法 (包含適當(dāng)?shù)膮?shù)適配, 返回類型適配, 和在調(diào)用參數(shù)前附加上捕獲的參數(shù)), 在這里為 #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V 方法的方法句柄.
6. MethodType instantiatedMethodType : 函數(shù)接口方法替換泛型為具體類型后的方法類型, 通常和 samMethodType 一樣, 不同的情況為泛型:
比如函數(shù)接口方法定義為 void accept(T t) T為泛型標(biāo)識, 這個時候方法類型為(Object)Void, 在編譯時T已確定, 即T由String替換, 這時samMethodType就是 (Object)Void, 而instantiatedMethodType為(String)Void.
第4, 5, 6 三個參數(shù)來自class文件中的. 如上面引導(dǎo)方法字節(jié)碼中Method arguments后面的三個參數(shù)就是將應(yīng)用于4, 5, 6的參數(shù).
Method arguments: #67 (Ljava/lang/Object;)V #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V #71 (Ljava/lang/String;)V
我們來看metafactory 的方法里的實現(xiàn)代碼
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(); }
在buildCallSite的函數(shù)中
CallSite buildCallSite() throws LambdaConversionException { final Class<?> innerClass = spinInnerClass();
函數(shù)spinInnerClass 構(gòu)建了這個內(nèi)部類,也就是生成了一個Lambda$$Lambda$1/716157500 這樣的內(nèi)部類,這個類是在運行的時候構(gòu)建的,并不會保存在磁盤中,如果想看到這個構(gòu)建的類,可以通過設(shè)置環(huán)境參數(shù)
System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");
會在你指定的路徑 . 當(dāng)前運行路徑上生成這個內(nèi)部類
3.靜態(tài)類
Java在編譯表達(dá)式的時候會生成lambda$0靜態(tài)私有類,在這個類里實現(xiàn)了表達(dá)式中的方法塊 system.out.println(s);
private static void lambda$0(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 #46 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: invokevirtual #50 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 7: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 8 0 s Ljava/lang/String;
當(dāng)然了在上一步通過設(shè)置的jdk.internal.lambda.dumpProxyClasses里生成的Lambda$$Lambda$1.class
public void accept(java.lang.Object); descriptor: (Ljava/lang/Object;)V flags: ACC_PUBLIC Code: stack=1, locals=2, args_size=2 0: aload_1 1: checkcast #15 // class java/lang/String 4: invokestatic #21 // Method Lambda.lambda$0:(Ljava/lang/String;)V 7: return RuntimeVisibleAnnotations: 0: #13()
調(diào)用了Lambda.lambda$0靜態(tài)函數(shù),也就是表達(dá)式中的函數(shù)塊
總結(jié)
這樣就完成的實現(xiàn)了Lambda表達(dá)式,使用invokedynamic指令,運行時調(diào)用LambdaMetafactory.metafactory動態(tài)的生成內(nèi)部類,實現(xiàn)了接口,內(nèi)部類里的調(diào)用方法塊并不是動態(tài)生成的,只是在原class里已經(jīng)編譯生成了一個靜態(tài)的方法,內(nèi)部類只需要調(diào)用該靜態(tài)方法
以上所述是小編給大家介紹的Java 8 動態(tài)類型語言Lambda表達(dá)式實現(xiàn)原理解析,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
SpringMVC攔截器和異常處理器使用示例超詳細(xì)講解
攔截器(Interceptor)是一種動態(tài)攔截方法調(diào)用的機制,在SpringMVC中動態(tài)攔截控制器方法的執(zhí)行。本文將詳細(xì)講講SpringMVC中攔截器參數(shù)及攔截器鏈配置,感興趣的可以嘗試一下2022-09-09SpringBoot整合Redis實現(xiàn)高并發(fā)數(shù)據(jù)緩存的示例講解
這篇文章主要介紹了SpringBoot整合Redis實現(xiàn)高并發(fā)數(shù)據(jù)緩存,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03Spring Boot + Mybatis多數(shù)據(jù)源和動態(tài)數(shù)據(jù)源配置方法
最近做項目遇到這樣的應(yīng)用場景,項目需要同時連接兩個不同的數(shù)據(jù)庫A, B,并且它們都為主從架構(gòu),一臺寫庫,多臺讀庫。下面小編給大家?guī)砹薙pring Boot + Mybatis多數(shù)據(jù)源和動態(tài)數(shù)據(jù)源配置方法,需要的朋友參考下吧2018-01-01SpringCloud微服務(wù)調(diào)用丟失請求頭的問題及解決方案
在Spring Cloud 中微服務(wù)之間的調(diào)用會用到Feign,但是在默認(rèn)情況下,Feign 調(diào)用遠(yuǎn)程服務(wù)存在Header請求頭丟失問題,下面給大家分享SpringCloud微服務(wù)調(diào)用丟失請求頭的問題及解決方案,感興趣的朋友一起看看吧2024-02-02SpringMVC?bean實現(xiàn)加載控制方法詳解
SpringMVC是一種基于Java,實現(xiàn)了Web?MVC設(shè)計模式,請求驅(qū)動類型的輕量級Web框架,即使用了MVC架構(gòu)模式的思想,將Web層進(jìn)行職責(zé)解耦?;谡埱篁?qū)動指的就是使用請求-響應(yīng)模型,框架的目的就是幫助我們簡化開發(fā),SpringMVC也是要簡化我們?nèi)粘eb開發(fā)2022-08-08