在我的想法裏,Lambda長得很奇怪,虛擬機真的認識這玩意嗎?還是說,Lambda經過編譯後,脫掉了僞裝的衣服,變成了大家熟知的方法?
對Lambda不熟悉的同學,可以先看我的另外兩篇文章。
先看以下的一個示例:
//使用註解@FunctionalInterface來聲明這是一個函數式接口
@FunctionalInterface
interface Print {
void output(String str);
}
public class Main {
private static void handle(String str, Print p) {
p.output(str);
}
public static void main(String[] args) {
handle("abc", str -> System.out.println(str));
}
}
運行後,顯然輸出abc。
那麼,這段代碼被編譯成什麼樣子了呢,我們使用javap -p Main.class查看編譯後的類成員信息(-p顯示所有的類和成員)
public class com.yang.testLambda.Main {
public com.yang.testLambda.Main();
private static void handle(java.lang.String, com.yang.testLambda.Print);
public static void main(java.lang.String[]);
private static void lambda$main$0(java.lang.String);
}
可以看到,多出來一個私有靜態方法lambda$main$0
那這個靜態方法,裏面的內容又是什麼呢?
繼續使用javap -c -p Main.class(-c對代碼進行反彙編)看看
public class com.yang.testLambda.Main {
public com.yang.testLambda.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
private static void handle(java.lang.String, com.yang.testLambda.Print);
Code:
0: aload_1
1: aload_0
2: invokeinterface #2, 2 // InterfaceMethod com/yang/testLambda/Print.output:(Ljava/lang/String;)V
7: return
public static void main(java.lang.String[]);
Code:
0: ldc #3 // String abc
2: invokedynamic #4, 0 // InvokeDynamic #0:output:()Lcom/yang/testLambda/Print;
7: invokestatic #5 // Method handle:(Ljava/lang/String;Lcom/yang/testLambda/Print;)V
10: return
private static void lambda$main$0(java.lang.String);
Code:
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
}
只看lambda$main$0方法,發現裏面是
new PrintStream("abc").println();
也就是
System.out.println("abc")
說明,生成的私有靜態方法裏面的內容就是lambda表達式裏面的主要內容。
那麼,這個私有靜態方法,是何時何地被誰調用的呢?
現在需要使用javac Main.java編譯成Main.class文件,之後使用java -Djdk.internal.lambda.dumpProxyClasses Main來運行,並會將運行過程中產生的內部類輸出出來。
運行第一個命令後,會產生Main.class和Print.class文件
運行第二個命令後,會額外產生Main$$Lambda$1.class文件
使用javap -c -p Main$$Lambda$1.class反編譯Main$$Lambda$1.class文件,會得到
final class Main$$Lambda$1 implements Print {
private Main$$Lambda$1();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
public void output(java.lang.String);
Code:
0: aload_1
1: invokestatic #18 // Method Main.lambda$main$0:(Ljava/lang/String;)V
4: return
}
發現Main$$Lambda$1實現了Print接口,並且output方法中,調用了Main類中的私有靜態方法lambda$main$0
那麼,該內部類又是何時何地被誰調用的呢?
而在一開始我們使用javap -c-p Main.class時,其中主方法是:
public static void main(java.lang.String[]);
Code:
0: ldc #3 // String abc
2: invokedynamic #4, 0 // InvokeDynamic #0:output:()Lcom/yang/testLambda/Print;
7: invokestatic #5 // Method handle:(Ljava/lang/String;Lcom/yang/testLambda/Print;)V
10: return
可以看得出這邊使用了invokedynamic調用了函數式接口,可以粗略的認爲這裏實例化了Print的實現類(其實具體的邏輯太挺複雜,這裏直接簡化了),就是內部類Main$$Lambda$1,然後調用靜態方法handle,這個方法接收一個字符串和Print實現類實例。
那麼,一開始的lambda表達式,可以改寫成這樣的形式:
interface Print {
void output(String str);
}
public class Main {
private static void handle(String str, Print p) {
p.output(str);
}
//編譯後生成的私有靜態方法,方法內容就是lambda裏的內容
private static void lambda$main$0(String str) {
System.out.println(str);
}
//運行時生成的內部類,實現函數式接口,實現方法中調用私有靜態方法
final class Main$$Lambda$1 implements Print {
@Override
public void output(String str) {
lambda$main$0(str);
}
}
public static void main(String[] args) {
Print print = new Main().new Main$$Lambda$1();
handle("abc", print);
}
}
到這裏,lambda表達式的執行原理,已經粗淺的解釋完畢了。