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方法中,内部类则放到新生成的类文件中