Lambda表达式与匿名内部类

1.Lambda表达式是什么

Java8中新引入的匿名函数功能,能够将匿名函数作为参数,相比传统的匿名内部类更加简洁。

2.Lambda表达式与匿名内部类有何区别

首先看一个简单的例子:

public class LambdaTest {
    public void fun1() {
        ((Runnable) () -> {
        }).run();
    }

    public void fun2() {
        new Runnable(){
            @Override
            public void run() {
            }
        }.run();
    }
}

fun1使用了lambda表达式,而fun2则使用了传统写法,为了比较两者的区别,我们可以对比两个方法编译后生成的字节码的区别:

通过对比上图的字节码可以发现:

  • fun2编译后生成了一个继承Runable的匿名内部类LambdaTest$1,在方法执行时对该类进行实例化后调用其run方法,整个执行逻辑所需的类在编译期已经生成
  • fun1编译后生成一个lambda$fun1$0方法和一个BootstrapMethod,在运行期时通过invokedynamic指令调用BootstrapMethod动态生成一个Runable类,该生成类中的run方法会调用编译期生成的lambda$fun1$0方法,最后通过invokeinterface调用生成类的run方法

由于lamda方法是运行时生成,所以常规方式无法看到其class文件,可以使用:java -Djdk.internal.lambda.dumpProxyClasses LambdaTest来输出动态生成的class文件:

3.如何读取外部变量

可以通过以下类来观察Lambda表达式和匿名内部类对:方法局部变量,类变量,实例变量,final常量的读取有何不同

public class LambdaTest {

    String str = "this is a lambda expression test.";
    static String str3 = "this is a lambda expression test.";
    final String str4 = "this is a lambda expression test.";

    public void fun1() {
        String str2 = str;
        ((Runnable) () -> {
            System.out.println("fun1 "+ str + str2 + str3 + str4);
        }).run();
    }

    public void fun2() {
        String str2 = str;
        new Runnable(){
            @Override
            public void run() {
                System.out.println("fun2 " + str + str2 + str3 + str4);
            }
        }.run();
    }
}


从上图可以看出,对于匿名内部类:

  • 方法局部变量:直接作为构造方法的参数被传递进匿名内部类
  • 类变量:getstatic指令从类中读取
  • 实例变量,final常量:通过对外部实例的引用(this$0)读取,此处是编译器做了优化,final常量直接被存储到常量池中了


对于Lambda表达式:

  • 方法局部变量:作为构造和lambda方法的参数传递
  • 类变量:getstatic指令从类中读取
  • 实例变量,final常量:通过对当前实例例的引用(this)读取

4.总结

Lambda表达式与内部类的最终表现形式非常相似,都是生成了一个新的类文件,区别在于:

  • 内部类在编译期生成新类,而Lambda借助invokedynamic指令在运行时动态生成,具有更高的灵活性
  • Lambda的执行逻辑保留在原类的lambda方法中,内部类则放到新生成的类文件中