java8lambda由淺入深上陕,通過一個簡單例子覆劈,逐步深入了解lambda實現(xiàn)原理荔睹。
一個簡單的java8中l(wèi)ambda例子
先看一個簡單的使用lambda的例子揖盘,我們從這個例子開始逐步探索java8中l(wèi)ambda是如何實現(xiàn)的楷力。
/**
* Created by qiyan on 2017/4/16.
*/
public class LambdaTest {
public static void main(String[] args) {
Func add = (x, y) -> x + y;
System.out.println(add.exec(1, 2));
}
}
@FunctionalInterface
interface Func {
int exec(int x, int y);
}
上面源碼編譯完成后執(zhí)行 javap -p -v -c LambdaTest 查看反編譯結(jié)果:
Classfile /Users/qiyan/src/test/src/main/java/LambdaTest.class
Last modified 2017-4-16; size 969 bytes
MD5 checksum 0a1db458a90b20fbfae645b576725fd4
Compiled from "LambdaTest.java"
public class LambdaTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#18 // java/lang/Object."<init>":()V
#2 = InvokeDynamic #0:#23 // #0:exec:()LFunc;
#3 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;
#4 = InterfaceMethodref #26.#27 // Func.exec:(II)I
#5 = Methodref #28.#29 // java/io/PrintStream.println:(I)V
#6 = Class #30 // LambdaTest
#7 = Class #31 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 main
#13 = Utf8 ([Ljava/lang/String;)V
#14 = Utf8 lambda$main$0
#15 = Utf8 (II)I
#16 = Utf8 SourceFile
#17 = Utf8 LambdaTest.java
#18 = NameAndType #8:#9 // "<init>":()V
#19 = Utf8 BootstrapMethods
#20 = MethodHandle #6:#32 // 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;
#21 = MethodType #15 // (II)I
#22 = MethodHandle #6:#33 // invokestatic LambdaTest.lambda$main$0:(II)I
#23 = NameAndType #34:#35 // exec:()LFunc;
#24 = Class #36 // java/lang/System
#25 = NameAndType #37:#38 // out:Ljava/io/PrintStream;
#26 = Class #39 // Func
#27 = NameAndType #34:#15 // exec:(II)I
#28 = Class #40 // java/io/PrintStream
#29 = NameAndType #41:#42 // println:(I)V
#30 = Utf8 LambdaTest
#31 = Utf8 java/lang/Object
#32 = Methodref #43.#44 // 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;
#33 = Methodref #6.#45 // LambdaTest.lambda$main$0:(II)I
#34 = Utf8 exec
#35 = Utf8 ()LFunc;
#36 = Utf8 java/lang/System
#37 = Utf8 out
#38 = Utf8 Ljava/io/PrintStream;
#39 = Utf8 Func
#40 = Utf8 java/io/PrintStream
#41 = Utf8 println
#42 = Utf8 (I)V
#43 = Class #46 // java/lang/invoke/LambdaMetafactory
#44 = NameAndType #47:#51 // 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;
#45 = NameAndType #14:#15 // lambda$main$0:(II)I
#46 = Utf8 java/lang/invoke/LambdaMetafactory
#47 = Utf8 metafactory
#48 = Class #53 // java/lang/invoke/MethodHandles$Lookup
#49 = Utf8 Lookup
#50 = Utf8 InnerClasses
#51 = 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;
#52 = Class #54 // java/lang/invoke/MethodHandles
#53 = Utf8 java/lang/invoke/MethodHandles$Lookup
#54 = Utf8 java/lang/invoke/MethodHandles
{
public 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 4: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:exec:()LFunc;
5: astore_1
6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_1
10: iconst_1
11: iconst_2
12: invokeinterface #4, 3 // InterfaceMethod Func.exec:(II)I
17: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
20: return
LineNumberTable:
line 7: 0
line 8: 6
line 9: 20
private static int lambda$main$0(int, int);
descriptor: (II)I
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: ireturn
LineNumberTable:
line 7: 0
}
SourceFile: "LambdaTest.java"
InnerClasses:
public static final #49= #48 of #52; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #20 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:
#21 (II)I
#22 invokestatic LambdaTest.lambda$main$0:(II)I
#21 (II)I
根據(jù)反編譯結(jié)果喊式,不難看出lambda表達式:(x, y) -> x + y 被編譯成了一個方法:lambda0
private static int lambda$main$0(int, int);
descriptor: (II)I
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: ireturn
LineNumberTable:
line 7: 0
翻譯成java代碼:
private static int lambda$main$0(int x, int y){
return x + y;
}
再看看main方法字節(jié)碼:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:exec:()LFunc;
5: astore_1
6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_1
10: iconst_1
11: iconst_2
12: invokeinterface #4, 3 // InterfaceMethod Func.exec:(II)I
17: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
20: return
LineNumberTable:
line 7: 0
line 8: 6
line 9: 20
執(zhí)行main步驟:
0:通過invokedynamic指令生成調(diào)用對象孵户;
5:存入本地變量表;
6:加載java.lang.System.out靜態(tài)方法岔留;
9:將lambda表達式生成的對象加載入執(zhí)行棧夏哭;
10:將int類型1加載入執(zhí)行棧;
11:將int類型2加載入執(zhí)行棧献联;
12:執(zhí)行l(wèi)ambda表達式生成的對象的exec方法方庭;
17:輸出執(zhí)行結(jié)果;
lambda的要點就在invokedynamic這個指令了酱固,通過invokedynamic指令生成目標對象械念,接下來我們了解一下invokedynamic指令。
invokedynamic指令
下面重點看看invokedynamic指令运悲,首先我們來看看常量池中出現(xiàn)的的InvokeDynamic類型:
官方文檔:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.10
CONSTANT_InvokeDynamic_info {
u1 tag;//InvokeDynamic類型標記18
u2 bootstrap_method_attr_index; //BootstrapMethods_attribute中的坐標
u2 name_and_type_index; //名字&類型常量池坐標
}
接下來看看BootstrapMethods_attribute龄减,InvokeDynamic中的bootstrap_method_attr_index就是指向其中bootstrap_methods的下標:
官方文檔:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.21
BootstrapMethods_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_bootstrap_methods;
{ u2 bootstrap_method_ref;//方法引用
u2 num_bootstrap_arguments;//參數(shù)數(shù)量
u2 bootstrap_arguments[num_bootstrap_arguments];//參數(shù)
} bootstrap_methods[num_bootstrap_methods];
}
繼續(xù)bootstrap_methods中的bootstrap_method_ref:
官方文檔:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.8
CONSTANT_MethodHandle_info {
u1 tag;//MethodHandle類型標記15
u1 reference_kind;//方法引用類型getfield/getstatic/putfield/putstatic/invokevirtual/invokestatic/invokespecial/new/invokeinterface,此例中使用的invokestatic班眯。
u2 reference_index;//引用類型希停,根據(jù)reference_kind確認,例如本例中kind為方法調(diào)用署隘,所以index為Methodref宠能,細節(jié)可以查看官方文檔。
}
回歸lambda例子
對invokedynamic學(xué)習(xí)總結(jié)一下:
invokedynamic指令通過找到BootstrapMethods中的方法磁餐,生成動態(tài)調(diào)用點违崇,對于本例,我們對照BootstrapMethods中的bootstrap_methods分析實現(xiàn):
BootstrapMethods:
0: #20 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:
#21 (II)I
#22 invokestatic LambdaTest.lambda$main$0:(II)I
#21 (II)I
按照上面指令诊霹,可以看出羞延,invokedynamic指令通過java.lang.invoke.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();
}
查看相關(guān)代碼可以看出脾还,metafactory就是核心方法伴箩,該方法通過InnerClassLambdaMetafactory類生成對象,供后續(xù)調(diào)用鄙漏,在InnerClassLambdaMetafactory源碼中可以看到嗤谚,有提供開關(guān)是否dump生成的class文件。
private static final ProxyClassesDumper dumper;
static {
final String key = "jdk.internal.lambda.dumpProxyClasses";
String path = AccessController.doPrivileged(new GetPropertyAction(key), null,new PropertyPermission(key , "read"));
dumper = (null == path) ? null : ProxyClassesDumper.getInstance(path);
}
//dump邏輯
if (dumper != null) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
dumper.dumpClass(lambdaClassName, classBytes);
return null;
}
}, null,
new FilePermission("<<ALL FILES>>", "read, write"),
// createDirectories may need it
new PropertyPermission("user.dir", "read"));
}
執(zhí)行下面命令生成中間對象java -Djdk.internal.lambda.dumpProxyClasses LambdaTest
Classfile /Users/qiyan/src/test/src/main/java/LambdaTest$$Lambda$1.class
Last modified 2017-4-17; size 236 bytes
MD5 checksum 983fa2b5e7d29c46d6f885925909b83e
final class LambdaTest$$Lambda$1 implements Func
minor version: 0
major version: 52
flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
Constant pool:
#1 = Utf8 LambdaTest$$Lambda$1
#2 = Class #1 // LambdaTest$$Lambda$1
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 Func
#6 = Class #5 // Func
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = NameAndType #7:#8 // "<init>":()V
#10 = Methodref #4.#9 // java/lang/Object."<init>":()V
#11 = Utf8 exec
#12 = Utf8 (II)I
#13 = Utf8 LambdaTest
#14 = Class #13 // LambdaTest
#15 = Utf8 lambda$main$0
#16 = NameAndType #15:#12 // lambda$main$0:(II)I
#17 = Methodref #14.#16 // LambdaTest.lambda$main$0:(II)I
#18 = Utf8 Code
{
private LambdaTest$$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 int exec(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: invokestatic #17 // Method LambdaTest.lambda$main$0:(II)I
5: ireturn
}
第一個例子學(xué)習(xí)分析到此結(jié)束怔蚌,下面是根據(jù)原理巩步,翻譯的等價的最終執(zhí)行代碼。
public class LambdaTest {
public static void main(String[] args) {
Func add = new LambdaTest$$Lambda$1();
System.out.println(add.exec(1, 2));
}
private static int lambda$main$0(int x, int y) {
return x + y;
}
static final class LambdaTest$$Lambda$1 implements Func {
private LambdaTest$$Lambda$1() {
}
public int exec(int x, int y) {
return LambdaTest.lambda$main$0(x, y);
}
}
}
@FunctionalInterface
interface Func {
int exec(int x, int y);
}
后續(xù)有時間繼續(xù)補充變量作用域相關(guān)實現(xiàn)原理...