Java 8 动态类型语言Lambda表达式实现原理解析
Java8支持动态语言,看到了很酷的Lambda表达式,对一直以静态类型语言自居的Java,让人看到了Java虚拟机可以支持动态语言的目标。
importjava.util.function.Consumer; publicclassLambda{ publicstaticvoidmain(String[]args){ Consumer<String>c=s->System.out.println(s); c.accept("hellolambda!"); } }
刚看到这个表达式,感觉java的处理方式是属于内部匿名类的方式
publicclassLambda{ static{ System.setProperty("jdk.internal.lambda.dumpProxyClasses","."); } publicstaticvoidmain(String[]args){ Consumer<String>c=newConsumer<String>(){ @Override publicvoidaccept(Strings){ System.out.println(s); } }; c.accept("hellolambda"); } }
编译的结果应该是Lambda.class,Lambda$1.class猜测在支持动态语言java换汤不换药,在最后编译的时候生成我们常见的方式。
但是结果不是这样的,只是产生了一个Lambda.class
反编译吧,来看看真相是什么?
javap-v-pLambda.class
注意 -p这个参数-p参数会显示所有的方法,而不带默认是不会反编译private的方法的
publicLambda(); descriptor:()V flags:ACC_PUBLIC Code: stack=1,locals=1,args_size=1 0:aload_0 1:invokespecial#21//Methodjava/lang/Object."<init>":()V 4:return LineNumberTable: line3:0 LocalVariableTable: StartLengthSlotNameSignature 050thisLLambda; publicstaticvoidmain(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//Stringhellolambda 9:invokeinterface#33,2//InterfaceMethodjava/util/function/Consumer.accept:(Ljava/lang/Object;)V 14:return LineNumberTable: line8:0 line9:6 line10:14 LocalVariableTable: StartLengthSlotNameSignature 0150args[Ljava/lang/String; 691cLjava/util/function/Consumer; LocalVariableTypeTable: StartLengthSlotNameSignature 691cLjava/util/function/Consumer<Ljava/lang/String;>; privatestaticvoidlambda$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//Fieldjava/lang/System.out:Ljava/io/PrintStream; 3:aload_0 4:invokevirtual#50//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V 7:return LineNumberTable: line8:0 LocalVariableTable: StartLengthSlotNameSignature 080sLjava/lang/String; } SourceFile:"Lambda.java" BootstrapMethods: 0:#66invokestaticjava/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; Methodarguments: #67(Ljava/lang/Object;)V #70invokestaticLambda.lambda$0:(Ljava/lang/String;)V #71(Ljava/lang/String;)V InnerClasses: publicstaticfinal#77=#73of#75;//Lookup=classjava/lang/invoke/MethodHandles$Lookupofclassjava/lang/invoke/MethodHandles
在这里我们发现了几个与我们常见的java不太一样的地方,由于常量定义太多了,文章中就不贴出了
1.Invokedynamic指令
Java的调用函数的四大指令(invokevirtual、invokespecial、invokestatic、invokeinterface),通常方法的符号引用在静态类型语言编译时就能产生,而动态类型语言只有在运行期才能确定接收者类型,改变四大指令的语意对java的版本有很大的影响,所以在JSR292《SupportingDynamicallyTypedLanguagesontheJavaPlatform》添加了一个新的指令
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指令的实例叫做一个动态调用点(dynamiccallsite),动态调用点最开始是未链接状态(unlinked:表示还未指定该调用点要调用的方法),动态调用点依靠引导方法来链接到具体的方法. 引导方法是由编译器生成,在运行期当JVM第一次遇到invokedynamic指令时,会调用引导方法来将invokedynamic指令所指定的名字(方法名,方法签名)和具体的执行代码(目标方法)链接起来,引导方法的返回值永久的决定了调用点的行为.引导方法的返回值类型是java.lang.invoke.CallSite,一个invokedynamic指令关联一个CallSite,将所有的调用委托到CallSite当前的target(MethodHandle)
InvokeDynamic#0就是BootstrapMethods表示#0的位置
0:#66invokestaticjava/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; Methodarguments: #67(Ljava/lang/Object;)V #70invokestaticLambda.lambda$0:(Ljava/lang/String;)V #71(Ljava/lang/String;)V
我们看到调用了LambdaMetaFactory.metafactory的方法
参数:
LambdaMetafactory.metafactory(Lookup,String,MethodType,MethodType,MethodHandle,MethodType)有六个参数,按顺序描述如下
1.MethodHandles.Lookupcaller:代表查找上下文与调用者的访问权限,使用invokedynamic指令时,JVM会自动自动填充这个参数
2.StringinvokedName:要实现的方法的名字,使用invokedynamic时,JVM自动帮我们填充(填充内容来自常量池InvokeDynamic.NameAndType.Name),在这里JVM为我们填充为"apply",即Consumer.accept方法名.
3.MethodTypeinvokedType:调用点期望的方法参数的类型和返回值的类型(方法signature).使用invokedynamic指令时,JVM会自动自动填充这个参数(填充内容来自常量池InvokeDynamic.NameAndType.Type),在这里参数为String,返回值类型为Consumer,表示这个调用点的目标方法的参数为String,然后invokedynamic执行完后会返回一个即Consumer实例.
4.MethodTypesamMethodType: 函数对象将要实现的接口方法类型,这里运行时,值为(Object)Object即Consumer.accept方法的类型(泛型信息被擦除).#67(Ljava/lang/Object;)V
5.MethodHandleimplMethod:一个直接方法句柄(DirectMethodHandle),描述在调用时将被执行的具体实现方法(包含适当的参数适配,返回类型适配,和在调用参数前附加上捕获的参数),在这里为#70invokestaticLambda.lambda$0:(Ljava/lang/String;)V方法的方法句柄.
6.MethodTypeinstantiatedMethodType:函数接口方法替换泛型为具体类型后的方法类型,通常和samMethodType一样,不同的情况为泛型:
比如函数接口方法定义为voidaccept(Tt) T为泛型标识,这个时候方法类型为(Object)Void, 在编译时T已确定,即T由String替换,这时samMethodType就是(Object)Void,而instantiatedMethodType为(String)Void.
第4,5,6三个参数来自class文件中的.如上面引导方法字节码中Methodarguments后面的三个参数就是将应用于4,5,6的参数.
Methodarguments: #67(Ljava/lang/Object;)V #70invokestaticLambda.lambda$0:(Ljava/lang/String;)V #71(Ljava/lang/String;)V
我们来看metafactory的方法里的实现代码
publicstaticCallSitemetafactory(MethodHandles.Lookupcaller, StringinvokedName, MethodTypeinvokedType, MethodTypesamMethodType, MethodHandleimplMethod, MethodTypeinstantiatedMethodType) throwsLambdaConversionException{ AbstractValidatingLambdaMetafactorymf; mf=newInnerClassLambdaMetafactory(caller,invokedType, invokedName,samMethodType, implMethod,instantiatedMethodType, false,EMPTY_CLASS_ARRAY,EMPTY_MT_ARRAY); mf.validateMetafactoryArgs(); returnmf.buildCallSite(); }
在buildCallSite的函数中
CallSitebuildCallSite()throwsLambdaConversionException{ finalClass<?>innerClass=spinInnerClass();
函数spinInnerClass构建了这个内部类,也就是生成了一个Lambda$$Lambda$1/716157500这样的内部类,这个类是在运行的时候构建的,并不会保存在磁盘中,如果想看到这个构建的类,可以通过设置环境参数
System.setProperty("jdk.internal.lambda.dumpProxyClasses",".");
会在你指定的路径.当前运行路径上生成这个内部类
3.静态类
Java在编译表达式的时候会生成lambda$0静态私有类,在这个类里实现了表达式中的方法块system.out.println(s);
privatestaticvoidlambda$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//Fieldjava/lang/System.out:Ljava/io/PrintStream; 3:aload_0 4:invokevirtual#50//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V 7:return LineNumberTable: line8:0 LocalVariableTable: StartLengthSlotNameSignature 080sLjava/lang/String;
当然了在上一步通过设置的jdk.internal.lambda.dumpProxyClasses里生成的Lambda$$Lambda$1.class
publicvoidaccept(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//classjava/lang/String 4:invokestatic#21//MethodLambda.lambda$0:(Ljava/lang/String;)V 7:return RuntimeVisibleAnnotations: 0:#13()
调用了Lambda.lambda$0静态函数,也就是表达式中的函数块
总结
这样就完成的实现了Lambda表达式,使用invokedynamic指令,运行时调用LambdaMetafactory.metafactory动态的生成内部类,实现了接口,内部类里的调用方法块并不是动态生成的,只是在原class里已经编译生成了一个静态的方法,内部类只需要调用该静态方法
以上所述是小编给大家介绍的Java8动态类型语言Lambda表达式实现原理解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!