Java8 增加了 Lambda 表達式,很大程度使代碼變的更加簡潔緊湊了,那麼 Java8 是如何實現 Lambda 表達式的呢? 直接看一個簡單的創建線程的例子。 執行 編譯生成文件 ,然後用 命令來分析這個class文件。 執行 顯示所有類和成員。 由上面的代碼可以看出編譯器根據 Lambda ...
Java8 增加了 Lambda 表達式,很大程度使代碼變的更加簡潔緊湊了,那麼 Java8 是如何實現 Lambda 表達式的呢?
直接看一個簡單的創建線程的例子。
public class TestLambda {
public static void main(String[] args) {
new Thread(() -> System.out.print("thread"));
}
}
執行javac TestLambda.java
編譯生成文件TestLambda.class
,然後用javap
命令來分析這個class文件。
執行javap -p TestLambda
顯示所有類和成員。
// javap -p TestLambda
Compiled from "TestLambda.java"
public class TestLambda {
public TestLambda();
public static void main(java.lang.String[]);
private static void lambda$main$0(); // 編譯後生成的
}
由上面的代碼可以看出編譯器根據 Lambda 表達式生成了一個私有靜態方法。
private static void lambda$main$0();
使用命令javap -v -p TestLambda
列印詳細信息。
public class TestLambda
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#24 // java/lang/Object."<init>":()V
#2 = Class #25 // java/lang/Thread
#3 = InvokeDynamic #0:#30 // #0:run:()Ljava/lang/Runnable;
#4 = Methodref #2.#31 // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
#5 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream;
#6 = String #34 // thread
#7 = Methodref #35.#36 // java/io/PrintStream.print:(Ljava/lang/String;)V
#8 = Class #37 // TestLambda
#9 = Class #38 // java/lang/Object
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 LocalVariableTable
#15 = Utf8 this
#16 = Utf8 LTestLambda;
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 lambda$main$0
#22 = Utf8 SourceFile
#23 = Utf8 TestLambda.java
#24 = NameAndType #10:#11 // "<init>":()V
#25 = Utf8 java/lang/Thread
#26 = Utf8 BootstrapMethods
#27 = MethodHandle #6:#39 // 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;
#28 = MethodType #11 // ()V
#29 = MethodHandle #6:#40 // invokestatic TestLambda.lambda$main$0:()V
#30 = NameAndType #41:#42 // run:()Ljava/lang/Runnable;
#31 = NameAndType #10:#43 // "<init>":(Ljava/lang/Runnable;)V
#32 = Class #44 // java/lang/System
#33 = NameAndType #45:#46 // out:Ljava/io/PrintStream;
#34 = Utf8 thread
#35 = Class #47 // java/io/PrintStream
#36 = NameAndType #48:#49 // print:(Ljava/lang/String;)V
#37 = Utf8 TestLambda
#38 = Utf8 java/lang/Object
#39 = Methodref #50.#51 // 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;
#40 = Methodref #8.#52 // TestLambda.lambda$main$0:()V
#41 = Utf8 run
#42 = Utf8 ()Ljava/lang/Runnable;
#43 = Utf8 (Ljava/lang/Runnable;)V
#44 = Utf8 java/lang/System
#45 = Utf8 out
#46 = Utf8 Ljava/io/PrintStream;
#47 = Utf8 java/io/PrintStream
#48 = Utf8 print
#49 = Utf8 (Ljava/lang/String;)V
#50 = Class #53 // java/lang/invoke/LambdaMetafactory
#51 = NameAndType #54:#58 // 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;
#52 = NameAndType #21:#11 // lambda$main$0:()V
#53 = Utf8 java/lang/invoke/LambdaMetafactory
#54 = Utf8 metafactory
#55 = Class #60 // java/lang/invoke/MethodHandles$Lookup
#56 = Utf8 Lookup
#57 = Utf8 InnerClasses
#58 = 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;
#59 = Class #61 // java/lang/invoke/MethodHandles
#60 = Utf8 java/lang/invoke/MethodHandles$Lookup
#61 = Utf8 java/lang/invoke/MethodHandles
{
public TestLambda();
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 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LTestLambda;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: new #2 // class java/lang/Thread
3: dup
4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
12: pop
13: return
LineNumberTable:
line 3: 0
line 4: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 args [Ljava/lang/String;
private static void lambda$main$0();
descriptor: ()V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String thread
5: invokevirtual #7 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
}
SourceFile: "TestLambda.java"
InnerClasses:
public static final #56= #55 of #59; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 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:
#28 ()V
#29 invokestatic TestLambda.lambda$main$0:()V
#28 ()V
如上所示:main() 方法創建線程的一行代碼反編譯後的位元組碼是
0: new #2 // class java/lang/Thread
3: dup
4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
12: pop
13: return
根據4: invokedynamic #3
找到常量池第三行
#3 = InvokeDynamic #0:#30 // #0:run:()Ljava/lang/Runnable;
其中,#0
指向BootstrapMethods:
的靜態工廠方法LambdaMetafactory.metafactory
BootstrapMethods:
0: #27 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:
#28 ()V
#29 invokestatic TestLambda.lambda$main$0:()V
#28 ()V
LambdaMetafactory.metafactory
會利用 asm 可以為 Lambda 表達式生成內部類,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();
}
為了查看這個內部類,加上生成代理類的參數運行 TestLambda,即java -Djdk.internal.lambda.dumpProxyClasses TestLambda
。運行結束後,發現在同級目錄生成了一個class文件TestLambda$$Lambda$1.class
。執行javap -v -p TestLambda$$Lambda$1
反編譯這個類。
final class TestLambda$$Lambda$1 implements java.lang.Runnable
minor version: 0
major version: 52
flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
Constant pool:
#1 = Utf8 TestLambda$$Lambda$1
#2 = Class #1 // TestLambda$$Lambda$1
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 java/lang/Runnable
#6 = Class #5 // java/lang/Runnable
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = NameAndType #7:#8 // "<init>":()V
#10 = Methodref #4.#9 // java/lang/Object."<init>":()V
#11 = Utf8 run
#12 = Utf8 Ljava/lang/invoke/LambdaForm$Hidden;
#13 = Utf8 TestLambda
#14 = Class #13 // TestLambda
#15 = Utf8 lambda$main$0
#16 = NameAndType #15:#8 // lambda$main$0:()V
#17 = Methodref #14.#16 // TestLambda.lambda$main$0:()V
#18 = Utf8 Code
#19 = Utf8 RuntimeVisibleAnnotations
{
private TestLambda$$Lambda$1();
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
public void run();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #17 // Method TestLambda.lambda$main$0:()V
3: return
RuntimeVisibleAnnotations:
0: #12()
}
由上可見,內部類TestLambda$$Lambda$1
實現了 java.lang.Runnable,run()
方法即線程啟動後執行的方法,它會觸發執行TestLambda.lambda$main$0:()
,即編譯TestLambda
時生成的私有靜態方法。
總結
創建線程的 Lambda 表達式在編譯後會在 class 文件新增一個私有靜態方法,運行這個類的期間,會使用 asm 操作位元組碼的技術,動態生成內部類,此內部類實現了 Runnable 介面,真正執行線程,線程方法 run 內部實際上就是調用了編譯後生成的私有靜態方法,從而執行線程代碼。