java8 lambda表达式原理

来源:互联网 发布:python ipython 编辑:程序博客网 时间:2024/05/20 01:38

java8 lambda表达式原理

java8已经推出有一段时间了,相信有不少公司已经把jdk升级到8了,每次jdk的升级都会带来一些性能以及应用上的优化,比如8移出了永久区,java.lang.OutOfMemoryError: PermGen space离我们而去,以及一些新的语法糖lambda,stream,默认方法等等,本文就来说说lambda表达式

lambda表达式写法

基本语法:
(parameters) -> expression

(parameters) ->{ statements; }

左边为传入参数,右边为执行代码,代表一个函数式接口的实现

lambda例子

此处不多写,贴出其他人的博客,请见Java8 lambda表达式10个示例

lambda表达式与旧的api对比

  • 对比 code
public class LambdaTest {    public static void main(String[] args) {        ArrayList<Integer> integers = new ArrayList<Integer>() ;        integers.add(9);        integers.add(8);        integers.add(7);        integers.add(6);        integers.sort(new Comparator<Integer>() {            public int compare(Integer o1, Integer o2) {                return o1.compareTo(o2);            }        });        System.out.println("匿名内部类排序输出:"+integers);        integers.sort((o1,o2)->o1.compareTo(02));        System.out.println("lambda1表达式排序输出:"+integers);        integers.sort(Integer::compareTo);        System.out.println("lambda2表达式排序输出:"+integers);    }}
  • 对比 输出
匿名内部类排序输出:[6, 7, 8, 9]lambda1表达式排序输出:[6, 7, 8, 9]lambda2表达式排序输出:[6, 7, 8, 9]
  • 编译后的文件
    这里写图片描述
    从上面看到,生成了两个class文件,一个是LambdaTest类的class文件,一个是Comparator匿名内部类的class文件,lambda表达式并未生成匿名内部类class文件,也就是说java8并不是靠编译器将lambda转换为匿名内部类,lambda脱糖过程稍后描述。

lambda原理

在java8中每一个Lambda表达式必须有一个函数式接口与之对应,那么函数式接口是什么呢?

什么是函数式接口

函数式接口(functional interface)简单来说就是只包含一个抽象方法的普通接口,java.lang.Runnable、java.util.Comparator都是函数式接口,java8提供了java.lang.FunctionalInterface注解进行标准,但是是非必须的,jdk会自动识别函数式接口。函数式接口可以被隐式转换为lambda表达式。

函数式与lambda表达式关系实例

  • 正确实例
public class LambdaTest2 {    public interface TestInterface{        public void test1();    }    public static void doSomething(TestInterface test){        test.test1();    }    public static void main(String[] args) {        doSomething(()-> System.out.println("HelloWorld"));    }}

输出HelloWorld

  • 错误实例
public class LambdaTest3 {    public interface TestInterface{        public void test1();        public void test2(int a);    }    public static void doSomething(TestInterface test){        test.test1();    }    public static void main(String[] args) {        doSomething(()-> System.out.println("HelloWorld"));    }}

编译错误:
Error:(19, 21) java: 不兼容的类型: com.chen.LambdaTest3.TestInterface 不是函数接口
在 接口 com.chen.LambdaTest3.TestInterface 中找到多个非覆盖抽象方法

那么lambda是怎么跟函数式接口对应的呢

来看一下LambdaTest2的字节码

Classfile /F:/workspace/java8-test/target/classes/com/chen/LambdaTest2.class  Last modified 2017-2-18; size 1376 bytes  MD5 checksum 5de06d632caf9ead069b79cf854411b9  Compiled from "LambdaTest2.java"public class com.chen.LambdaTest2  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPERConstant pool:   #1 = Methodref          #9.#31         // java/lang/Object."<init>":()V   #2 = InterfaceMethodref #10.#32        // com/chen/LambdaTest2$TestInterface.test1:()V   #3 = InvokeDynamic      #0:#37         // #0:test1:()Lcom/chen/LambdaTest2$TestInterface;   #4 = Methodref          #8.#38         // com/chen/LambdaTest2.doSomething:(Lcom/chen/LambdaTest2$TestInterface;)V   #5 = Fieldref           #39.#40        // java/lang/System.out:Ljava/io/PrintStream;   #6 = String             #41            // HelloWorld   #7 = Methodref          #42.#43        // java/io/PrintStream.println:(Ljava/lang/String;)V   #8 = Class              #44            // com/chen/LambdaTest2   #9 = Class              #45            // java/lang/Object  #10 = Class              #46            // com/chen/LambdaTest2$TestInterface  #11 = Utf8               TestInterface  #12 = Utf8               InnerClasses  #13 = Utf8               <init>  #14 = Utf8               ()V  #15 = Utf8               Code  #16 = Utf8               LineNumberTable  #17 = Utf8               LocalVariableTable  #18 = Utf8               this  #19 = Utf8               Lcom/chen/LambdaTest2;  #20 = Utf8               doSomething  #21 = Utf8               (Lcom/chen/LambdaTest2$TestInterface;)V  #22 = Utf8               test  #23 = Utf8               Lcom/chen/LambdaTest2$TestInterface;  #24 = Utf8               main  #25 = Utf8               ([Ljava/lang/String;)V  #26 = Utf8               args  #27 = Utf8               [Ljava/lang/String;  #28 = Utf8               lambda$main$0  #29 = Utf8               SourceFile  #30 = Utf8               LambdaTest2.java  #31 = NameAndType        #13:#14        // "<init>":()V  #32 = NameAndType        #47:#14        // test1:()V  #33 = Utf8               BootstrapMethods  #34 = MethodHandle       #6:#48         // 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;  #35 = MethodType         #14            //  ()V  #36 = MethodHandle       #6:#49         // invokestatic com/chen/LambdaTest2.lambda$main$0:()V  #37 = NameAndType        #47:#50        // test1:()Lcom/chen/LambdaTest2$TestInterface;  #38 = NameAndType        #20:#21        // doSomething:(Lcom/chen/LambdaTest2$TestInterface;)V  #39 = Class              #51            // java/lang/System  #40 = NameAndType        #52:#53        // out:Ljava/io/PrintStream;  #41 = Utf8               HelloWorld  #42 = Class              #54            // java/io/PrintStream  #43 = NameAndType        #55:#56        // println:(Ljava/lang/String;)V  #44 = Utf8               com/chen/LambdaTest2  #45 = Utf8               java/lang/Object  #46 = Utf8               com/chen/LambdaTest2$TestInterface  #47 = Utf8               test1  #48 = Methodref          #57.#58        // 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;  #49 = Methodref          #8.#59         // com/chen/LambdaTest2.lambda$main$0:()V  #50 = Utf8               ()Lcom/chen/LambdaTest2$TestInterface;  #51 = Utf8               java/lang/System  #52 = Utf8               out  #53 = Utf8               Ljava/io/PrintStream;  #54 = Utf8               java/io/PrintStream  #55 = Utf8               println  #56 = Utf8               (Ljava/lang/String;)V  #57 = Class              #60            // java/lang/invoke/LambdaMetafactory  #58 = NameAndType        #61:#64        // 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;  #59 = NameAndType        #28:#14        // lambda$main$0:()V  #60 = Utf8               java/lang/invoke/LambdaMetafactory  #61 = Utf8               metafactory  #62 = Class              #66            // java/lang/invoke/MethodHandles$Lookup  #63 = Utf8               Lookup  #64 = Utf8               (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;  #65 = Class              #67            // java/lang/invoke/MethodHandles  #66 = Utf8               java/lang/invoke/MethodHandles$Lookup  #67 = Utf8               java/lang/invoke/MethodHandles{  public com.chen.LambdaTest2();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: return      LineNumberTable:        line 8: 0      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       5     0  this   Lcom/chen/LambdaTest2;  public static void doSomething(com.chen.LambdaTest2$TestInterface);    descriptor: (Lcom/chen/LambdaTest2$TestInterface;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokeinterface #2,  1            // InterfaceMethod com/chen/LambdaTest2$TestInterface.test1:()V         6: return      LineNumberTable:        line 15: 0        line 16: 6      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       7     0  test   Lcom/chen/LambdaTest2$TestInterface;  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=1, locals=1, args_size=1         0: invokedynamic #3,  0              // InvokeDynamic #0:test1:()Lcom/chen/LambdaTest2$TestInterface;         5: invokestatic  #4                  // Method doSomething:(Lcom/chen/LambdaTest2$TestInterface;)V         8: return      LineNumberTable:        line 18: 0        line 19: 8      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       9     0  args   [Ljava/lang/String;}SourceFile: "LambdaTest2.java"InnerClasses:     public static #11= #10 of #8; //TestInterface=class com/chen/LambdaTest2$TestInterface of class com/chen/LambdaTest2     public static final #63= #62 of #65; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandlesBootstrapMethods:  0: #34 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:      #35 ()V      #36 invokestatic com/chen/LambdaTest2.lambda$main$0:()V      #35 ()V

其中main方法

 public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=1, locals=1, args_size=1         0: invokedynamic #3,  0              // InvokeDynamic #0:test1:()Lcom/chen/LambdaTest2$TestInterface;         5: invokestatic  #4                  // Method doSomething:(Lcom/chen/LambdaTest2$TestInterface;)V         8: return      LineNumberTable:        line 18: 0        line 19: 8      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       9     0  args   [Ljava/lang/String;

执行了三条指令 invokedynamic、invokestatic、return

  • 第一条是lambda表达式转化为函数式接口TestInterface
  • 第二条执行doSomething方法
  • 第三条退出main方法

invokedynamic指令是在jvm7中新增的,invokedynamic出现的位置代表一个动态调用点
invokedynamic指令后面会跟一个指向常量池的调用点限定符,这个限定符会被解析为一个动态调用点。
调用点限定符的符号引用为CONSTANT_InvokeDynamic_info结构

CONSTANT_InvokeDynamic_info{      u1 tag;      u2 bootstrap_method_attr_index;       u2 name_and_type_index;  }  

依据这个可以找到对应的动态调用引导方法Java.lang.invoke.CallSite

此处invokedynamic后面跟的是常量#3,#3指向#0和#37,#37代表TestInterface接口的test1方法

#3 = InvokeDynamic      #0:#37         // #0:test1:()Lcom/chen/LambdaTest2$TestInterface;#37 = NameAndType        #47:#50        // test1:()Lcom/chen/LambdaTest2$TestInterface;

而#0在字节码最后的BootstrapMethods中,Method arguments#36代表这个lambda表达式调用代码

BootstrapMethods:  0: #34 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:      #35 ()V      #36 invokestatic com/chen/LambdaTest2.lambda$main$0:()V      #35 ()V

根据BootstrapMethods对应的#34可以找到此处lambda InvokeDynamic指令对应的引导方法是LambdaMetafactory.metafactory,其返还一个CallSite

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

观察源码可得,其通过new一个InnerClassLambdaMetafactory并调用buildCallSite方法创造了类似内部类的lambda CallSite
有兴趣可以看buildCallSite方法的源码
InnerClassLambdaMetafactory类的源码注释是

Lambda metafactory implementation which dynamically creates an inner-class-like class per lambda callsite.

至此,lambda表达式的脱糖过程已经了解完成,其中很多细节设计太多java字节码知识,本汪也不是太了解,有兴趣可以多看看java虚拟机规范

至于为什么要绕一圈生成一个内部类的动态调用点然后执行,而不是直接把lambda编译成内部类,也许是为了减少编译后的文件数,具体不得而知,有待研究

原创粉丝点击