Java 8支持动态语言,看到了很酷的Lambda表达式,对一直以静态类型语言自居的Java,让人看到了Java虚拟机可以支持动态语言的目标。
1
2
3
4
5
6
7
|
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!" ); } } |
刚看到这个表达式,感觉java的处理方式是属于内部匿名类的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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" ); } } |
编译的结果应该是Lambda.class , Lambda$1.class 猜测在支持动态语言java换汤不换药,在最后编译的时候生成我们常见的方式。
但是结果不是这样的,只是产生了一个Lambda.class
反编译吧,来看看真相是什么?
1
|
javap -v -p Lambda. class |
注意 -p 这个参数 -p 参数会显示所有的方法,而不带默认是不会反编译private 的方法的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
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 |
在这里我们发现了几个与我们常见的java不太一样的地方,由于常量定义太多了,文章中就不贴出了
1. Invokedynamic 指令
Java的调用函数的四大指令(invokevirtual、invokespecial、invokestatic、invokeinterface),通常方法的符号引用在静态类型语言编译时就能产生,而动态类型语言只有在运行期才能确定接收者类型,改变四大指令的语意对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指令的实例叫做一个动态调用点(dynamic call site), 动态调用点最开始是未链接状态(unlinked:表示还未指定该调用点要调用的方法), 动态调用点依靠引导方法来链接到具体的方法. 引导方法是由编译器生成, 在运行期当JVM第一次遇到invokedynamic指令时, 会调用引导方法来将invokedynamic指令所指定的名字(方法名,方法签名)和具体的执行代码(目标方法)链接起来, 引导方法的返回值永久的决定了调用点的行为.引导方法的返回值类型是java.lang.invoke.CallSite, 一个invokedynamic指令关联一个CallSite, 将所有的调用委托到CallSite当前的target(MethodHandle)
InvokeDynamic #0 就是BootstrapMethods表示#0的位置
1
2
3
4
5
|
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 |
我们看到调用了LambdaMetaFactory.metafactory 的方法
参数:
LambdaMetafactory.metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)有六个参数, 按顺序描述如下
1. MethodHandles.Lookup caller : 代表查找上下文与调用者的访问权限, 使用invokedynamic指令时, JVM会自动自动填充这个参数
2. String invokedName : 要实现的方法的名字, 使用invokedynamic时, JVM自动帮我们填充(填充内容来自常量池InvokeDynamic.NameAndType.Name), 在这里JVM为我们填充为 "apply", 即Consumer.accept方法名.
3. MethodType invokedType : 调用点期望的方法参数的类型和返回值的类型(方法signature). 使用invokedynamic指令时, JVM会自动自动填充这个参数(填充内容来自常量池InvokeDynamic.NameAndType.Type), 在这里参数为String, 返回值类型为Consumer, 表示这个调用点的目标方法的参数为String, 然后invokedynamic执行完后会返回一个即Consumer实例.
4. MethodType samMethodType : 函数对象将要实现的接口方法类型, 这里运行时, 值为 (Object)Object 即 Consumer.accept方法的类型(泛型信息被擦除).#67 (Ljava/lang/Object;)V
5. MethodHandle implMethod : 一个直接方法句柄(DirectMethodHandle), 描述在调用时将被执行的具体实现方法 (包含适当的参数适配, 返回类型适配, 和在调用参数前附加上捕获的参数), 在这里为 #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V 方法的方法句柄.
6. MethodType instantiatedMethodType : 函数接口方法替换泛型为具体类型后的方法类型, 通常和 samMethodType 一样, 不同的情况为泛型:
比如函数接口方法定义为 void accept(T t) T为泛型标识, 这个时候方法类型为(Object)Void, 在编译时T已确定, 即T由String替换, 这时samMethodType就是 (Object)Void, 而instantiatedMethodType为(String)Void.
第4, 5, 6 三个参数来自class文件中的. 如上面引导方法字节码中Method arguments后面的三个参数就是将应用于4, 5, 6的参数.
1
2
3
4
|
Method arguments: # 67 (Ljava/lang/Object;)V # 70 invokestatic Lambda.lambda$ 0 :(Ljava/lang/String;)V # 71 (Ljava/lang/String;)V |
我们来看metafactory 的方法里的实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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的函数中
1
2
|
CallSite buildCallSite() throws LambdaConversionException { final Class<?> innerClass = spinInnerClass(); |
函数spinInnerClass 构建了这个内部类,也就是生成了一个Lambda$$Lambda$1/716157500 这样的内部类,这个类是在运行的时候构建的,并不会保存在磁盘中,如果想看到这个构建的类,可以通过设置环境参数
1
|
System.setProperty( "jdk.internal.lambda.dumpProxyClasses" , "." ); |
会在你指定的路径 . 当前运行路径上生成这个内部类
3.静态类
Java在编译表达式的时候会生成lambda$0静态私有类,在这个类里实现了表达式中的方法块 system.out.println(s);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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; |
当然了在上一步通过设置的jdk.internal.lambda.dumpProxyClasses里生成的Lambda$$Lambda$1.class
1
2
3
4
5
6
7
8
9
10
11
|
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 () |
调用了Lambda.lambda$0静态函数,也就是表达式中的函数块
总结
这样就完成的实现了Lambda表达式,使用invokedynamic指令,运行时调用LambdaMetafactory.metafactory动态的生成内部类,实现了接口,内部类里的调用方法块并不是动态生成的,只是在原class里已经编译生成了一个静态的方法,内部类只需要调用该静态方法
以上所述是小编给大家介绍的Java 8 动态类型语言Lambda表达式实现原理解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
原文链接:http://blog.csdn.net/raintungli/article/details/54910152