Java8的Lambda表达式

    Java8有两大新特性,一个是Lambda表达式,第二个是Stream API。虽然平时在工作中写起来挺爽的,但是一直不理解底层是怎么运行起来的,所以本文尝试着分析一波Lambda表达式,如果有说的不对的地方,请各位帮忙指正一下呀!

 

Lambda表达式介绍:

    可以简单理解为传递匿名函数的一种方式,它没有名称,只有参数列表,函数主体,返回类型,可能还有可以抛出的异常列表等等。个人理解它重要用法是简化匿名内部类的代码编写。例如:

Java8的Lambda表达式_第1张图片

  两者是等价的,可以看出来下面这种使用了Lambda之后,代码要少很多!Java8中的Lambda的基本语法如下所示:

     (parameters) ->  expression

     (parameters) -> { statements; }

  但是要注意的是,并非所有的匿名内部类都可以写成这种形式,只有当它符合函数式接口(接口上有@FunctionalInterface标记),才可以使用Lambda表达式。

 

那什么又是函数式接口呢?

    所谓的函数式接口其实就是只定义一个抽象方法的接口,函数式接口出现的地方都可以将代码简写成Lambda表达式,例如上面举例的Runnable:

Java8的Lambda表达式_第2张图片

    这个抽象方法被称为函数描述符,例如上面的这个run(),它描述的就是这个接口什么参数也不接收,什么参数也不反悔。你编写的代码如果不符合这个描述会报错,报错是由编译器做的。

    Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。

 

Java8中定义了多种函数式接口:

    有很多,这里举几个例子说明下,前者为接口名称,后者为它的描述符:

    Predicete     T –> boolean   :接收T泛型值,返回一个boolean值

    Consumer      T -> void       :接收T泛型值,什么都不返回

    Function    T -> R          :接收T泛型值,返回R泛型值

    ……(其他的可见“参考二”,总结的挺全面的)

   每种接口都定义了不同的函数描述符。了解了以上内容之后,我们再来看下Lambda表达式最终是怎么被执行的呢。

 

 

Lambda表达式最终是怎么被执行的呢?

使用 javap -v -p .\Tets.class 反编译文件,查看字节码信息,这里截取了曲中的一部分内容:

 4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;

 

  private static void lambda$main$0();
    descriptor: ()V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #11                 // String hello
         5: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 8: 0
}
SourceFile: "Tets.java"
InnerClasses:
     public static final #83= #82 of #87; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #36 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:
      #37 ()V
      #38 invokestatic Tets.lambda$main$0:()V
      #37 ()V
  1: #36 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:
      #45 (Ljava/lang/Object;)V
      #46 invokevirtual java/io/PrintStream.print:(Ljava/lang/String;)V
      #47 (Ljava/lang/String;)V

可以看到,Lambda表达式所在处会产生一个invokedynamic的指令,在之前的《JVM是如何实现方法调用的》曾经提到过Invokedynamic指令:

其他四中调用方法的指令是把符号引用替换为直接引用,直接指到内存中的地址,也就是说他们需要指导方法所在类名,方法名以及方法描述符。但是像Lambda表达式这种,只要方法描述符对上了,可以使用引用到其他类中的方法(任意Function类中的apply)。Invokedynamic就是这么一个指令,允许程序将调用点连接到任意符合条件的方法上。

 

除了这个指令之外,还生成了一个Bootstrap Method方法,Bootstrap Method是java.lang.invoke.LambdaMetafactory中的一个静态方法,它返回一个CallSite对象。从#38可以看出,这个方法的第二个入参就是我们编写的Lambda表达式中的内容,即这样CallSite就和Lambda表达式联系在一起了。

总结一下就是:

         1.Lambda表达的内容被编译成了当前类的一个静态或实例方法.

         2.Lambda表达式所在处会产生一条invokedynamic指令调用, 同时编译器会生成一个对应的Bootstrap Method.

         3.当JVM第一次碰到这条invokedynamic时, 会调用对应的Bootstrap方法.

         4.由Lambda表达式产生的invokedynamic指令的引导方法是调用LambdaMetafactory.metafactory()方法.

         5.调用引导方法会返回一个CallSite对象实例

         6.执行MethodHanlde代表的方法, 返回结果, 结果为动态生成的接口实例, 接口实现调用“1”中生成的方法.


 

 

参考:

    《Java8实战》

    https://blog.csdn.net/lz710117239/article/details/76192629(Java8种的函数式接口)

    https://www.cnblogs.com/jqqiang/p/7482823.html(内联函数)

    https://blog.csdn.net/hj7jay/article/details/73480386(方法句柄与反射速度比较)

    https://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf(Lambda性能测试报告)

    https://www.2cto.com/kf/201604/502490.html(Lambda底层字节码分析)

 

 

 

 

你可能感兴趣的:(JAVA8)