Lambda 表达式和等效的匿名 class
Lambda expression and equivalent anonymous class
如果我有一个方法将单方法接口作为参数,我可以这样调用它:
foo(new Bar() {
@Override
public String baz(String qux) {
return modify(qux) + transmogrify(qux);
}
}
但是如果我必须在一个紧密的循环中调用 foo
数百万次,我可能更愿意避免在每次循环中都创建匿名 class 的新实例:
final Bar bar = new Bar() {
@Override
public String baz(String qux) {
return modify(qux) + transmogrify(qux);
}
};
while (...) {
foo(bar);
}
现在,如果我用 lambda 表达式替换匿名 class:
while (...) {
foo(qux -> modify(qux) + transmogrify(qux));
}
这个 lambda 表达式是否等同于上述匿名示例中的第一个或第二个片段 class?
我认为它更像第二个,因为 lambda 只会创建一个 Bar
实例。
我写一个测试class:
public class LambdaTest {
//create a new runnable for each loop
void test1(){
while (true){
invoke(new Runnable() {
@Override
public void run() {
System.out.println("---");
}
});
}
}
//create only one Runnable
void test2(){
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("---");
}
};
while (true){
invoke(runnable);
}
}
//use lambda
void test3(){
while (true){
invoke(()->{
System.out.println("---");
});
}
}
private void invoke(Runnable runnable){
}
}
这里是每个方法的字节码:
test1:
0: aload_0
1: new #2 // class LambdaTest
4: dup
5: aload_0
6: invokespecial #3 // Method LambdaTest."<init>":(LLambdaTest;)V
9: invokevirtual #4 // Method invoke:(Ljava/lang/Runnable;)V
12: goto 0
test2:
0: new #5 // class LambdaTest
3: dup
4: aload_0
5: invokespecial #6 // Method LambdaTest."<init>":(LLambdaTest;)V
8: astore_1
9: aload_0
10: aload_1
11: invokevirtual #4 // Method invoke:(Ljava/lang/Runnable;)V
14: goto 9
test3:
0: aload_0
1: invokedynamic #7, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
6: invokevirtual #4 // Method invoke:(Ljava/lang/Runnable;)V
9: goto 0
在 test1 中,每个循环都会创建一个新的匿名实例 class 并调用 init 方法。
在test2中,只有一个匿名的实例class,并且循环中的操作码数量最少。
在test3中,与test2的唯一区别是在循环中的invokevirtual
操作码之前添加了一个invokedynamic
。
根据this article,invokedynamic
第一次会调用bootstrap方法来创建匿名实例class,之后会使用在其余生中创建的实例。
所以我的建议是:随时使用 lambda,不必担心开销。
如果我有一个方法将单方法接口作为参数,我可以这样调用它:
foo(new Bar() {
@Override
public String baz(String qux) {
return modify(qux) + transmogrify(qux);
}
}
但是如果我必须在一个紧密的循环中调用 foo
数百万次,我可能更愿意避免在每次循环中都创建匿名 class 的新实例:
final Bar bar = new Bar() {
@Override
public String baz(String qux) {
return modify(qux) + transmogrify(qux);
}
};
while (...) {
foo(bar);
}
现在,如果我用 lambda 表达式替换匿名 class:
while (...) {
foo(qux -> modify(qux) + transmogrify(qux));
}
这个 lambda 表达式是否等同于上述匿名示例中的第一个或第二个片段 class?
我认为它更像第二个,因为 lambda 只会创建一个 Bar
实例。
我写一个测试class:
public class LambdaTest {
//create a new runnable for each loop
void test1(){
while (true){
invoke(new Runnable() {
@Override
public void run() {
System.out.println("---");
}
});
}
}
//create only one Runnable
void test2(){
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("---");
}
};
while (true){
invoke(runnable);
}
}
//use lambda
void test3(){
while (true){
invoke(()->{
System.out.println("---");
});
}
}
private void invoke(Runnable runnable){
}
}
这里是每个方法的字节码:
test1:
0: aload_0
1: new #2 // class LambdaTest
4: dup
5: aload_0
6: invokespecial #3 // Method LambdaTest."<init>":(LLambdaTest;)V
9: invokevirtual #4 // Method invoke:(Ljava/lang/Runnable;)V
12: goto 0
test2:
0: new #5 // class LambdaTest
3: dup
4: aload_0
5: invokespecial #6 // Method LambdaTest."<init>":(LLambdaTest;)V
8: astore_1
9: aload_0
10: aload_1
11: invokevirtual #4 // Method invoke:(Ljava/lang/Runnable;)V
14: goto 9
test3:
0: aload_0
1: invokedynamic #7, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
6: invokevirtual #4 // Method invoke:(Ljava/lang/Runnable;)V
9: goto 0
在 test1 中,每个循环都会创建一个新的匿名实例 class 并调用 init 方法。
在test2中,只有一个匿名的实例class,并且循环中的操作码数量最少。
在test3中,与test2的唯一区别是在循环中的invokevirtual
操作码之前添加了一个invokedynamic
。
根据this article,invokedynamic
第一次会调用bootstrap方法来创建匿名实例class,之后会使用在其余生中创建的实例。
所以我的建议是:随时使用 lambda,不必担心开销。