lambda表达式的底层实现

来源:互联网 发布:名片地图生成软件 编辑:程序博客网 时间:2024/05/22 00:11

首先来看一段简单的代码

@FunctionalInterfaceinterface ILambdaCaculator{int result(int a,int b);}public class LambdaTest {public static void main(String[] args) {System.out.println("add :"+LambdaUse((a,b)-> a+b,12,14));}public static int LambdaUse(ILambdaCaculator lambda,int a,int b){return lambda.result(a, b);}}

代码在static main方法中使用了一个Lambda表达式,然后完成a+b的操作。

如果大家对lambda表达式不熟悉的话,可以先阅读我的另一篇博文,Lambda使用详解。另外呢,还是建议大家先看我的另一篇博文,java内部类相关的底层实现。

那这个lambda表达式编译器是怎么处理的呢?

我们反编译一个LambdaTest类的class文件,一看究竟。

命令:

javap -p LambdaTest.class 
Compiled from "LambdaTest.java"public class share_work_impl.LambdaTest {  public share_work_impl.LambdaTest();  public static void main(java.lang.String[]);  public static int LambdaUse(share_work_impl.ILambdaCaculator, int, int);  private static int lambda$0(int, int);}

发现很神奇的地方了, private static int lambda$0(int, int);这个方法我们没有定义啊,怎么来的呢?其实这个方法就是对应这个(a,b)-> a+b,12,14)lambda表达式的,也就是说,编译器会为每一个lambda表达式生成一个私有的lambda$x方法,这个private static int lambda$0(int, int);方法是静态的。难道所有的表达式都是生成静态、私有的lambda$x方法吗?还是因为我的这个lambda表达式是写在main这个静态方法中才导致是生成静态方法的??

我们把表达式改到普通成员方法中看看。

代码改成如下:

@FunctionalInterfaceinterface ILambdaCaculator {int result(int a, int b);}public class LambdaTest {public static void main(String[] args) {LambdaTest test=new LambdaTest();    test.LambdaUse(12,23);}public void LambdaUse(int a,int b) { ILambdaCaculator lambdaCaculator=(x,y)->x+y; System.out.println("a+b="+lambdaCaculator.result(a, b));}}

我们接着反编译LambdaTest类的class文件。

命令:

javap -p LambdaTest.class 
Compiled from "LambdaTest.java"public class share_work_impl.LambdaTest {  public share_work_impl.LambdaTest();  public static void main(java.lang.String[]);  public void LambdaUse(int, int);  private static int lambda$0(int, int);}

咦,发现也是静态的耶。难道就真的是全部的lambda表达式都会生成一个静态的方法和它相对吗?

在给出一段代码:

@FunctionalInterfaceinterface ILambdaCaculator {int result(int a, int b);}public class LambdaTest {private int c=123;public static void main(String[] args) {LambdaTest test=new LambdaTest();    test.LambdaUse(12,23);}public void LambdaUse(int a,int b) { ILambdaCaculator lambdaCaculator=(x,y)->x+y+c; System.out.println("a+b+c="+lambdaCaculator.result(a, b));}}

再给出反编译后的代码。

命令:

javap -p LambdaTest.class 
Compiled from "LambdaTest.java"public class share_work_impl.LambdaTest {  private int c;  public share_work_impl.LambdaTest();  public static void main(java.lang.String[]);  public void LambdaUse(int, int);  private int lambda$0(int, int);}

什么!!!!private int lambda$0(int, int);

不是静态的,到底发生了什么?大家仔细看上面的代码。大家是不是感觉和之前的差不多,感觉现在就像是找两张图片的不同。哈哈哈。

其实改动的地方就是

@FunctionalInterfaceinterface ILambdaCaculator {int result(int a, int b);}public class LambdaTest {private int c=123;//增加了一个成员变量public static void main(String[] args) {LambdaTest test=new LambdaTest();    test.LambdaUse(12,23);}public void LambdaUse(int a,int b) { ILambdaCaculator lambdaCaculator=(x,y)->x+y+c;//在lambda表达式中调用了这个成员变量 System.out.println("a+b+c="+lambdaCaculator.result(a, b));}}

哦,原来这样。当lambda表达式调用了外部的成员变量或者方法的时候,生成的lambda$x方法就只是私有的了,并没有静态。

lambda表达式是只是生成了这个方法吗?想想,肯定不是,要是只有这个方法,谁来调用呢?肯定还生成了其他东西,没错,还成了class,但是默认这个class不会写在文件中,所以我们要给编译器配置一下。

eclipse中选中java文件,右键。


然后在VM arguments中输入-Djdk.internal.lambda.dumpProxyClasses

然后执行文件。


然后发现在项目的与src同文件夹下多了一个文件夹,这个文件夹就是lambda表达式所在的包名,里面就存放了对应lambda表达式的类。


发现生成了LambdaTest$$Lambda$1.class这么一个class,你会发现,它的这个格式和内部类差别好大,LambdaTest$$Lambda$1.class比内部类多了$$Lambda这个部分,可能是为了区分吧,这个我也不太清楚。

因为我们的lambda表达式访问了外面类的成员,是不是这个class也会拿到外部类的引用呢?看看这个class吧。

命令:

javap -p LambdaTest\$\$Lambda\$1.class 
final class share_work_impl.LambdaTest$$Lambda$1 implements share_work_impl.ILambdaCaculator {  private final share_work_impl.LambdaTest arg$1;  private share_work_impl.LambdaTest$$Lambda$1(share_work_impl.LambdaTest);  private static share_work_impl.ILambdaCaculator get$Lambda(share_work_impl.LambdaTest);  public int result(int, int);}

通过这句话 private final share_work_impl.LambdaTest arg$1;

确认是拿到了外部类的一个引用,那又回到了内部类的问题,这个引用怎么来另一个class中访问它的私有的成员的方法的呢?会不会像内部类那样通过静态的access$x方法呢?

我们还是使用命令看一下:

命令: 

javap -p -c LambdaTest\$\$Lambda\$1.class
final class share_work_impl.LambdaTest$$Lambda$1 implements share_work_impl.ILambdaCaculator {  private final share_work_impl.LambdaTest arg$1;  private share_work_impl.LambdaTest$$Lambda$1(share_work_impl.LambdaTest);    Code:       0: aload_0       1: invokespecial #13                 // Method java/lang/Object."<init>":()V       4: aload_0       5: aload_1       6: putfield      #15                 // Field arg$1:Lshare_work_impl/LambdaTest;       9: return  private static share_work_impl.ILambdaCaculator get$Lambda(share_work_impl.LambdaTest);    Code:       0: new           #2                  // class share_work_impl/LambdaTest$$Lambda$1       3: dup       4: aload_0       5: invokespecial #19                 // Method "<init>":(Lshare_work_impl/LambdaTest;)V       8: areturn  public int result(int, int);    Code:       0: aload_0       1: getfield      #15                 // Field arg$1:Lshare_work_impl/LambdaTest;       4: iload_1       5: iload_2       6: invokespecial #27                 // Method share_work_impl/LambdaTest.lambda$0:(II)I       9: ireturn}

主要看public int result(int, int);方法下的第6行,invokespecial #27                 // Method share_work_impl/LambdaTest.lambda$0:(II)I。可以看到,这里是直接调用这个LambdaTest.lambda$0方法的。它不是像内部类使用静态的access$x方法这种方式,那它是怎么调用私有的方法和成员的呢?这个问题,我现在不能给出明确的答案,如果大家知道了,要记得留言告诉我哦。还有一个问题,

这个代码:

@FunctionalInterfaceinterface ILambdaCaculator {int result(int a, int b);}public class LambdaTest {public int c=123;public static void main(String[] args) {LambdaTest test=new LambdaTest();    test.LambdaUse(12,23);}public void LambdaUse(int a,int b) { ILambdaCaculator lambdaCaculator=(x,y)->x+y+c; System.out.println("a+b+c="+lambdaCaculator.result(a, b));}private int getC(){return c;}}

反编译代码

final class share_work_impl.LambdaTest$$Lambda$1 implements share_work_impl.ILambdaCaculator {  private final share_work_impl.LambdaTest arg$1;  private share_work_impl.LambdaTest$$Lambda$1(share_work_impl.LambdaTest);  private static share_work_impl.ILambdaCaculator get$Lambda(share_work_impl.LambdaTest);  public int result(int, int);}

那这个反编译代码的private static share_work_impl.ILambdaCaculator get$Lambda(share_work_impl.LambdaTest);这个方法的目的是干什么的?

如果上面的代码中的lambda表达式没有访问外部类的成员,会有这个方法吗?

@FunctionalInterfaceinterface ILambdaCaculator {int result(int a, int b);}public class LambdaTest {public int c=123;public static void main(String[] args) {LambdaTest test=new LambdaTest();    test.LambdaUse(12,23);}public void LambdaUse(int a,int b) { ILambdaCaculator lambdaCaculator=(x,y)->x+y; System.out.println("a+b+c="+lambdaCaculator.result(a, b));}private int getC(){return c;}}

那生成的Lambda$x方法就是静态的。那生成的class呢?

反编译一下。

final class share_work_impl.LambdaTest$$Lambda$1 implements share_work_impl.ILambdaCaculator {  private share_work_impl.LambdaTest$$Lambda$1();    Code:       0: aload_0       1: invokespecial #10                 // Method java/lang/Object."<init>":()V       4: return  public int result(int, int);    Code:       0: iload_1       1: iload_2       2: invokestatic  #18                 // Method share_work_impl/LambdaTest.lambda$0:(II)I       5: ireturn}
发现一个问题,就是没有访问外部类成员的时候,就没有了这个方法private static share_work_impl.ILambdaCaculator get$Lambda(share_work_impl.LambdaTest);上面访问了外部类成员变量就有这个方法,那这个方法的真正作用是什么呢??我们来看看这个方法干了什么事情。

private static share_work_impl.ILambdaCaculator get$Lambda(share_work_impl.LambdaTest);    Code:       0: new           #2                  // class share_work_impl/LambdaTest$$Lambda$1       3: dup       4: aload_0       5: invokespecial #19                 // Method "<init>":(Lshare_work_impl/LambdaTest;)V       8: areturn

简单的说就是创建了当前class所代表类的实例并向上转型成share_work_impl.ILambdaCaculator接口类型。

为什么lambda表达式访问了外部类成员或者方法就有这个 private static share_work_impl.ILambdaCaculator get$Lambda(share_work_impl.LambdaTest);

方法,而如果没有访问外部类成员就没有这个方法。如果是说限制外部类不能调用lambda表达式里面的内容,那应该两种情况都要这种方法啊。我目前还没有想明白这个问题。如果大家知道的,要记得留言给我哦,还有上面那个问题哦。