Java 8 動(dòng)態(tài)類(lèi)型語(yǔ)言Lambda表達(dá)式實(shí)現(xiàn)原理解析
Java 8支持動(dòng)態(tài)語(yǔ)言,看到了很酷的Lambda表達(dá)式,對(duì)一直以靜態(tài)類(lèi)型語(yǔ)言自居的Java,讓人看到了Java虛擬機(jī)可以支持動(dòng)態(tài)語(yǔ)言的目標(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!");
}
}
剛看到這個(gè)表達(dá)式,感覺(jué)java的處理方式是屬于內(nèi)部匿名類(lè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 猜測(cè)在支持動(dòng)態(tài)語(yǔ)言java換湯不換藥,在最后編譯的時(shí)候生成我們常見(jiàn)的方式。
但是結(jié)果不是這樣的,只是產(chǎn)生了一個(gè)Lambda.class
反編譯吧,來(lái)看看真相是什么?
javap -v -p Lambda.class
注意 -p 這個(gè)參數(shù) -p 參數(shù)會(huì)顯示所有的方法,而不帶默認(rèn)是不會(huì)反編譯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)了幾個(gè)與我們常見(jiàn)的java不太一樣的地方,由于常量定義太多了,文章中就不貼出了
1. Invokedynamic 指令
Java的調(diào)用函數(shù)的四大指令(invokevirtual、invokespecial、invokestatic、invokeinterface),通常方法的符號(hào)引用在靜態(tài)類(lèi)型語(yǔ)言編譯時(shí)就能產(chǎn)生,而動(dòng)態(tài)類(lèi)型語(yǔ)言只有在運(yùn)行期才能確定接收者類(lèi)型,改變四大指令的語(yǔ)意對(duì)java的版本有很大的影響,所以在JSR 292 《Supporting Dynamically Typed Languages on the Java Platform》添加了一個(gè)新的指令
Invokedynamic
0: invokedynamic #30, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
#30 是代表常量#30 也就是后面的注釋InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
0 是占位符號(hào),目前無(wú)用
2. BootstrapMethods
每一個(gè)invokedynamic指令的實(shí)例叫做一個(gè)動(dòng)態(tài)調(diào)用點(diǎn)(dynamic call site), 動(dòng)態(tài)調(diào)用點(diǎn)最開(kāi)始是未鏈接狀態(tài)(unlinked:表示還未指定該調(diào)用點(diǎn)要調(diào)用的方法), 動(dòng)態(tài)調(diào)用點(diǎn)依靠引導(dǎo)方法來(lái)鏈接到具體的方法. 引導(dǎo)方法是由編譯器生成, 在運(yùn)行期當(dāng)JVM第一次遇到invokedynamic指令時(shí), 會(huì)調(diào)用引導(dǎo)方法來(lái)將invokedynamic指令所指定的名字(方法名,方法簽名)和具體的執(zhí)行代碼(目標(biāo)方法)鏈接起來(lái), 引導(dǎo)方法的返回值永久的決定了調(diào)用點(diǎn)的行為.引導(dǎo)方法的返回值類(lèi)型是java.lang.invoke.CallSite, 一個(gè)invokedynamic指令關(guān)聯(lián)一個(gè)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)有六個(gè)參數(shù), 按順序描述如下
1. MethodHandles.Lookup caller : 代表查找上下文與調(diào)用者的訪問(wèn)權(quán)限, 使用invokedynamic指令時(shí), JVM會(huì)自動(dòng)自動(dòng)填充這個(gè)參數(shù)
2. String invokedName : 要實(shí)現(xiàn)的方法的名字, 使用invokedynamic時(shí), JVM自動(dòng)幫我們填充(填充內(nèi)容來(lái)自常量池InvokeDynamic.NameAndType.Name), 在這里JVM為我們填充為 "apply", 即Consumer.accept方法名.
3. MethodType invokedType : 調(diào)用點(diǎn)期望的方法參數(shù)的類(lèi)型和返回值的類(lèi)型(方法signature). 使用invokedynamic指令時(shí), JVM會(huì)自動(dòng)自動(dòng)填充這個(gè)參數(shù)(填充內(nèi)容來(lái)自常量池InvokeDynamic.NameAndType.Type), 在這里參數(shù)為String, 返回值類(lèi)型為Consumer, 表示這個(gè)調(diào)用點(diǎn)的目標(biāo)方法的參數(shù)為String, 然后invokedynamic執(zhí)行完后會(huì)返回一個(gè)即Consumer實(shí)例.
4. MethodType samMethodType : 函數(shù)對(duì)象將要實(shí)現(xiàn)的接口方法類(lèi)型, 這里運(yùn)行時(shí), 值為 (Object)Object 即 Consumer.accept方法的類(lèi)型(泛型信息被擦除).#67 (Ljava/lang/Object;)V
5. MethodHandle implMethod : 一個(gè)直接方法句柄(DirectMethodHandle), 描述在調(diào)用時(shí)將被執(zhí)行的具體實(shí)現(xiàn)方法 (包含適當(dāng)?shù)膮?shù)適配, 返回類(lèi)型適配, 和在調(diào)用參數(shù)前附加上捕獲的參數(shù)), 在這里為 #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V 方法的方法句柄.
6. MethodType instantiatedMethodType : 函數(shù)接口方法替換泛型為具體類(lèi)型后的方法類(lèi)型, 通常和 samMethodType 一樣, 不同的情況為泛型:
比如函數(shù)接口方法定義為 void accept(T t) T為泛型標(biāo)識(shí), 這個(gè)時(shí)候方法類(lèi)型為(Object)Void, 在編譯時(shí)T已確定, 即T由String替換, 這時(shí)samMethodType就是 (Object)Void, 而instantiatedMethodType為(String)Void.
第4, 5, 6 三個(gè)參數(shù)來(lái)自class文件中的. 如上面引導(dǎo)方法字節(jié)碼中Method arguments后面的三個(gè)參數(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
我們來(lái)看metafactory 的方法里的實(shí)現(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)建了這個(gè)內(nèi)部類(lèi),也就是生成了一個(gè)Lambda$$Lambda$1/716157500 這樣的內(nèi)部類(lèi),這個(gè)類(lèi)是在運(yùn)行的時(shí)候構(gòu)建的,并不會(huì)保存在磁盤(pán)中,如果想看到這個(gè)構(gòu)建的類(lèi),可以通過(guò)設(shè)置環(huán)境參數(shù)
System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");
會(huì)在你指定的路徑 . 當(dāng)前運(yùn)行路徑上生成這個(gè)內(nèi)部類(lèi)
3.靜態(tài)類(lèi)
Java在編譯表達(dá)式的時(shí)候會(huì)生成lambda$0靜態(tài)私有類(lèi),在這個(gè)類(lèi)里實(shí)現(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)然了在上一步通過(guò)設(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é)
這樣就完成的實(shí)現(xiàn)了Lambda表達(dá)式,使用invokedynamic指令,運(yùn)行時(shí)調(diào)用LambdaMetafactory.metafactory動(dòng)態(tài)的生成內(nèi)部類(lèi),實(shí)現(xiàn)了接口,內(nèi)部類(lèi)里的調(diào)用方法塊并不是動(dòng)態(tài)生成的,只是在原class里已經(jīng)編譯生成了一個(gè)靜態(tài)的方法,內(nèi)部類(lèi)只需要調(diào)用該靜態(tài)方法
以上所述是小編給大家介紹的Java 8 動(dòng)態(tài)類(lèi)型語(yǔ)言Lambda表達(dá)式實(shí)現(xiàn)原理解析,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Java17和springboot3.0使用shiro報(bào)ClassNotFoundException的解決
本文主要介紹了Java17和springboot3.0使用shiro報(bào)ClassNotFoundException的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-04-04
SpringBoot中日志切面實(shí)現(xiàn)小結(jié)
本文介紹了SpringBoot中日志切面實(shí)現(xiàn)小結(jié),通過(guò)定義一個(gè)自定義注解和創(chuàng)建一個(gè)日志切面類(lèi),為方法添加日志記錄功能,感興趣的可以了解一下2024-11-11
java中xml進(jìn)行報(bào)文發(fā)送和解析操作
這篇文章主要介紹了java中xml進(jìn)行報(bào)文發(fā)送和解析操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
SWT(JFace) 圖片瀏覽器 實(shí)現(xiàn)代碼
SWT(JFace)小制作:圖片瀏覽器2009-06-06
Java Web Filter 過(guò)濾器學(xué)習(xí)教程(推薦)
Filter也稱之為過(guò)濾器,它是Servlet技術(shù)中最激動(dòng)人心的技術(shù).這篇文章主要介紹了Java Web Filter 過(guò)濾器學(xué)習(xí)教程的相關(guān)資料,需要的朋友可以參考下2016-05-05
Java8中使用流方式查詢數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了Java8中使用流方式查詢數(shù)據(jù)庫(kù)的相關(guān)資料,需要的朋友可以參考下2016-01-01
JAVA隨機(jī)數(shù)隨機(jī)字母的實(shí)現(xiàn)(微信搶紅包小練習(xí))
這篇文章主要介紹了JAVA隨機(jī)數(shù)隨機(jī)字母的實(shí)現(xiàn)(微信搶紅包小練習(xí)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04

