欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-07 09:11:20
java 8支持动态语言,看到了很酷的lambda表达式,对一直以静态类型语言自居的java,让人看到了java虚拟机可以支持动态语言的目标。 import ja...

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>(){ 
      @override 
      public 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: ()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的位置

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;)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

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表达式实现原理解析,希望对大家有所帮助