Java 8 动态类型语言Lambda表达式实现原理分析

Java 8支持动态语言,看到了很酷的Lambda表达式,对一直以静态类型语言自居的Java,让人看到了Java虚拟机可以支持动态语言的目标。

  1. import java.util.function.Consumer;  
  2.   
  3. public class Lambda {  
  4.     public static void main(String[] args) {  
  5.         Consumer<String> c = s -> System.out.println(s);  
  6.         c.accept("hello lambda!");  
  7.     }  
  8. }  
刚看到这个表达式,感觉java的处理方式是属于内部匿名类的方式
  1. public class Lambda {  
  2.     static {  
  3.         System.setProperty("jdk.internal.lambda.dumpProxyClasses"".");  
  4.     }  
  5.     public static void main(String[] args) {  
  6.         Consumer<String> c = new Consumer<String>(){  
  7.             @Override  
  8.             public void accept(String s) {  
  9.                 System.out.println(s);  
  10.             }  
  11.             };  
  12.         c.accept("hello lambda");  
  13.     }  
  14. }  

编译的结果应该是Lambda.class , Lambda$1.class 猜测在支持动态语言java换汤不换药,在最后编译的时候生成我们常见的方式。

但是结果不是这样的,只是产生了一个Lambda.class 

反编译吧,来看看真相是什么?

  1. javap -v -p Lambda.class   

注意  -p 这个参数 -p 参数会显示所有的方法,而不带默认是不会反编译private 的方法的

  1.   public Lambda();  
  2.     descriptor: ()V  
  3.     flags: ACC_PUBLIC  
  4.     Code:  
  5.       stack=1, locals=1, args_size=1  
  6.          0: aload_0  
  7.          1: invokespecial #21                 // Method java/lang/Object."<init>":()V  
  8.          4return  
  9.       LineNumberTable:  
  10.         line 30  
  11.       LocalVariableTable:  
  12.         Start  Length  Slot  Name   Signature  
  13.             0       5     0  this   LLambda;  
  14.   
  15.   public static void main(java.lang.String[]);  
  16.     descriptor: ([Ljava/lang/String;)V  
  17.     flags: ACC_PUBLIC, ACC_STATIC  
  18.     Code:  
  19.       stack=2, locals=2, args_size=1  
  20.          0: invokedynamic #30,  0             // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;  
  21.          5: astore_1  
  22.          6: aload_1  
  23.          7: ldc           #31                 // String hello lambda  
  24.          9: invokeinterface #33,  2           // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V  
  25.         14return  
  26.       LineNumberTable:  
  27.         line 80  
  28.         line 96  
  29.         line 1014  
  30.       LocalVariableTable:  
  31.         Start  Length  Slot  Name   Signature  
  32.             0      15     0  args   [Ljava/lang/String;  
  33.             6       9     1     c   Ljava/util/function/Consumer;  
  34.       LocalVariableTypeTable:  
  35.         Start  Length  Slot  Name   Signature  
  36.             6       9     1     c   Ljava/util/function/Consumer<Ljava/lang/String;>;  
  37.   
  38.   private static void lambda$0(java.lang.String);  
  39.     descriptor: (Ljava/lang/String;)V  
  40.     flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC  
  41.     Code:  
  42.       stack=2, locals=1, args_size=1  
  43.          0: getstatic     #46                 // Field java/lang/System.out:Ljava/io/PrintStream;  
  44.          3: aload_0  
  45.          4: invokevirtual #50                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V  
  46.          7return  
  47.       LineNumberTable:  
  48.         line 80  
  49.       LocalVariableTable:  
  50.         Start  Length  Slot  Name   Signature  
  51.             0       8     0     s   Ljava/lang/String;  
  52. }  
  53. SourceFile: "Lambda.java"  
  54. BootstrapMethods:  
  55.   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;  
  56.     Method arguments:  
  57.       #67 (Ljava/lang/Object;)V  
  58.       #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V  
  59.       #71 (Ljava/lang/String;)V  
  60. InnerClasses:  
  61.      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

  1. 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. 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;  
  2.   Method arguments:  
  3.     #67 (Ljava/lang/Object;)V  
  4.     #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V  
  5.     #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. Method arguments:  
  2.     #67 (Ljava/lang/Object;)V  
  3.     #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V  
  4.     #71 (Ljava/lang/String;)V  

我们来看metafactory 的方法里的实现代码
  1. public static CallSite metafactory(MethodHandles.Lookup caller,  
  2.                                        String invokedName,  
  3.                                        MethodType invokedType,  
  4.                                        MethodType samMethodType,  
  5.                                        MethodHandle implMethod,  
  6.                                        MethodType instantiatedMethodType)  
  7.             throws LambdaConversionException {  
  8.         AbstractValidatingLambdaMetafactory mf;  
  9.         mf = new InnerClassLambdaMetafactory(caller, invokedType,  
  10.                                              invokedName, samMethodType,  
  11.                                              implMethod, instantiatedMethodType,  
  12.                                              false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);  
  13.         mf.validateMetafactoryArgs();  
  14.         return mf.buildCallSite();  
  15.     }  

在buildCallSite的函数中
  1. CallSite buildCallSite() throws LambdaConversionException {  
  2.         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. private static void lambda$0(java.lang.String);  
  2.     descriptor: (Ljava/lang/String;)V  
  3.     flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC  
  4.     Code:  
  5.       stack=2locals=1args_size=1  
  6.          0: getstatic     #46                 // Field java/lang/System.out:Ljava/io/PrintStream;  
  7.          3: aload_0  
  8.          4: invokevirtual #50                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V  
  9.          7: return  
  10.       LineNumberTable:  
  11.         line 8: 0  
  12.       LocalVariableTable:  
  13.         Start  Length  Slot  Name   Signature  
  14.             0       8     0     s   Ljava/lang/String;  


当然了在上一步通过设置的jdk.internal.lambda.dumpProxyClasses里生成的Lambda$$Lambda$1.class

  1. public void accept(java.lang.Object);  
  2.    descriptor: (Ljava/lang/Object;)V  
  3.    flags: ACC_PUBLIC  
  4.    Code:  
  5.      stack=1locals=2args_size=2  
  6.         0: aload_1  
  7.         1: checkcast     #15                 // class java/lang/String  
  8.         4: invokestatic  #21                 // Method Lambda.lambda$0:(Ljava/lang/String;)V  
  9.         7: return  
  10.    RuntimeVisibleAnnotations:  
  11.      0: #13()  

调用了Lambda.lambda$0静态函数,也就是表达式中的函数块

总结

这样就完成的实现了Lambda表达式,使用invokedynamic指令,运行时调用LambdaMetafactory.metafactory动态的生成内部类,实现了接口,内部类里的调用方法块并不是动态生成的,只是在原class里已经编译生成了一个静态的方法,内部类只需要调用该静态方法

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值