Java 8 动态类型语言Lambda表达式实现原理分析
Java 8支持动态语言,看到了很酷的Lambda表达式,对一直以静态类型语言自居的Java,让人看到了Java虚拟机可以支持动态语言的目标。
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的处理方式是属于内部匿名类的方式
public class Lambda {static {System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");}public static void main(String[] args) {Consumer<String> c = new Consumer<String>(){@Overridepublic void accept(String s) {System.out.println(s);}};c.accept("hello lambda");}
}
编译的结果应该是Lambda.class , Lambda$1.class 猜测在支持动态语言java换汤不换药,在最后编译的时候生成我们常见的方式。
但是结果不是这样的,只是产生了一个Lambda.class
反编译吧,来看看真相是什么?
javap -v -p Lambda.class
注意 -p 这个参数 -p 参数会显示所有的方法,而不带默认是不会反编译private 的方法的
public Lambda();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #21 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this LLambda;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: invokedynamic #30, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;5: astore_16: aload_17: ldc #31 // String hello lambda9: invokeinterface #33, 2 // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V14: returnLineNumberTable:line 8: 0line 9: 6line 10: 14LocalVariableTable:Start Length Slot Name Signature0 15 0 args [Ljava/lang/String;6 9 1 c Ljava/util/function/Consumer;LocalVariableTypeTable:Start Length Slot Name Signature6 9 1 c Ljava/util/function/Consumer<Ljava/lang/String;>;private static void lambda$0(java.lang.String);descriptor: (Ljava/lang/String;)Vflags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETICCode:stack=2, locals=1, args_size=10: getstatic #46 // Field java/lang/System.out:Ljava/io/PrintStream;3: aload_04: invokevirtual #50 // Method java/io/PrintStream.println:(Ljava/lang/String;)V7: returnLineNumberTable:line 8: 0LocalVariableTable:Start Length Slot Name Signature0 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 #0 就是BootstrapMethods表示#0的位置
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的参数.
Method arguments:#67 (Ljava/lang/Object;)V#70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V#71 (Ljava/lang/String;)V
我们来看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();}
在buildCallSite的函数中
CallSite buildCallSite() throws LambdaConversionException {final Class<?> innerClass = spinInnerClass();
函数spinInnerClass 构建了这个内部类,也就是生成了一个Lambda$$Lambda$1/716157500 这样的内部类,这个类是在运行的时候构建的,并不会保存在磁盘中,如果想看到这个构建的类,可以通过设置环境参数
System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");
会在你指定的路径 . 当前运行路径上生成这个内部类
3.静态类
Java在编译表达式的时候会生成lambda$0静态私有类,在这个类里实现了表达式中的方法块 system.out.println(s);
private static void lambda$0(java.lang.String);descriptor: (Ljava/lang/String;)Vflags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETICCode:stack=2, locals=1, args_size=10: getstatic #46 // Field java/lang/System.out:Ljava/io/PrintStream;3: aload_04: invokevirtual #50 // Method java/io/PrintStream.println:(Ljava/lang/String;)V7: returnLineNumberTable:line 8: 0LocalVariableTable:Start Length Slot Name Signature0 8 0 s Ljava/lang/String;
当然了在上一步通过设置的jdk.internal.lambda.dumpProxyClasses里生成的Lambda$$Lambda$1.class
public void accept(java.lang.Object);descriptor: (Ljava/lang/Object;)Vflags: ACC_PUBLICCode:stack=1, locals=2, args_size=20: aload_11: checkcast #15 // class java/lang/String4: invokestatic #21 // Method Lambda.lambda$0:(Ljava/lang/String;)V7: returnRuntimeVisibleAnnotations:0: #13()
调用了Lambda.lambda$0静态函数,也就是表达式中的函数块
总结
这样就完成的实现了Lambda表达式,使用invokedynamic指令,运行时调用LambdaMetafactory.metafactory动态的生成内部类,实现了接口,内部类里的调用方法块并不是动态生成的,只是在原class里已经编译生成了一个静态的方法,内部类只需要调用该静态方法
Java 8 动态类型语言Lambda表达式实现原理分析相关推荐
- java动态语言_java动态类型语言支持(三)
invokedynamic指令 在前面java动态类型语言支持(一)(二)中我们有提到invokedynamic指令和java.lang.invoke包中的MethodHandle机制,在某种程度上他 ...
- java 复合方法_《Java 8 实战》Ch3: Lambda表达式(下):类型与限制、方法引用、复合...
李文轩 2019-04-23 3.5 类型的检查和判断:变量捕获限制 类型检查 Lambda的类型是从使用Lambda的上下文推断出来的. Lambda表达式需要的类型称为目标类型 . Lambda表 ...
- JVM:方法调用之动态类型语言支持。
Java虚拟机的字节码指令集的数量从Sun公司的第一款Java虚拟机问世至JDK 7来临之前的十余年时间里,一致没有发生任何变化.随着JDK 7的发布,字节码指令集终于迎来了第一位新成员--invok ...
- 虚拟机字节码执行引擎——动态类型语言支持
目录 动态类型语言 Java与动态类型 java.lang.invoke包 invokedynamic指令 实战:掌控方法分派规则 随着JDK 7的发布的字节码首位新成员--invokedynamic ...
- 【Java】Java8新特性Lambda表达式和Stream API
Lambda表达式是实现支持函数式编程技术的基础. 函数式编程与面向对象编程:函数式编程将程序代码看作数学中的函数,函数本身作为另一个函数参数或返回值,即高阶函数.而面向对象编程按照真实世界客观事物的 ...
- 【Java 8 in Action】Lambda表达式
文章目录 Lambda表达式 Lambda表达式的介绍 Lambda表达式的语法 Method References Default methods Functional Interface 浅析La ...
- 编译型语言、解释型语言、静态类型语言、动态类型语言概念与区别
编译型语言和解释型语言 1.编译型语言 需通过编译器(compiler)将源代码编译成机器码,之后才能执行的语言.一般需经过编译(compile).链接(linker)这两个步骤.编译是把源代码编译成 ...
- 动态类型语言和静态类型语言
我们通常说的动态语言.静态语言就是指的动态类型语言和静态类型语言. 动态类型语言 动态类型语言是指在运行期间才去做数据类型检查的语言.也就是说,在用动态类型的语言编程时,永远不用给任何变量指定数据类型 ...
- 概念区别 【编译型语言与解释型语言、动态类型语言与静态类型语言、强类型语言与弱类型语言】
概念区别 [编译型语言与解释型语言.动态类型语言与静态类型语言.强类型语言与弱类型语言] 文章目录 一.编译型语言与解释型语言 二.动态类型语言与静态类型语言 三.强类型语言与弱类型语言 总结 一.编 ...
最新文章
- 知名美国服务器租用商介绍
- 通过输入流获取的xml格式字符串转为json和map格式
- wordpress php7 mysql_WordPress可以使用PHP7的MySQLi扩展
- 架构垂直伸缩和水平伸缩区别_简单的可伸缩图神经网络
- cf卡序列号修改工具_王者荣耀无需Root修改荣耀战区软件和方法分享 全国地区可任意修改...
- python压缩算法_用python实现LZ78压缩算法
- 严重抗议:被砍了个尾巴
- calabash android教程,Calabash Android 使用教程 (二)
- graphpad细胞增殖曲线_Graphpad 作图教程 | 这份超详细的生存曲线绘制指南,科研新手一看就会!...
- 程序员这口饭,职业规划解决方案---程序员职业规划(二)
- 培养使用计算机的良好道德规范,浅谈如何提高学生学习信息技术的兴趣
- 【mmdetection】mmdetection学习率设置
- ps,pr,3Dmax软件使用经验
- python libusb_python, libusb, pyusb
- Android 列表视图制作古诗词
- Android SERVICE后台服务进程的守护
- pta7-2 公路村村通
- 快速删除工作表中所有的文本框
- 推荐一个完全免费的ue4模型资源论坛
- 皮一下之笑出腹肌的台词