Lambda 表达式和等效的匿名类

k31*_*159 8 java lambda anonymous-class

如果我有一个将单方法接口作为参数的方法,我可以这样调用它:

foo(new Bar() {
    @Override
    public String baz(String qux) {
        return modify(qux) + transmogrify(qux);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是如果我必须foo在一个紧密循环中调用数百万次,我可能更愿意避免每次通过循环创建匿名类的新实例:

final Bar bar = new Bar() {
        @Override
        public String baz(String qux) {
            return modify(qux) + transmogrify(qux);
        }
    };

while (...) {
    foo(bar);
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我用 lambda 表达式替换匿名类:

while (...) {
    foo(qux -> modify(qux) + transmogrify(qux));
}
Run Code Online (Sandbox Code Playgroud)

这个 lambda 表达式是否等同于上述匿名类示例中的第一个或第二个片段?

hao*_*ang 2

Bar我认为它更像是第二个,因为lambda只会创建一个实例。

我写了一个测试类:

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){
    }
}
Run Code Online (Sandbox Code Playgroud)

这是每个方法的字节码:

test1:
         0: aload_0
         1: new           #2                  // class LambdaTest$1
         4: dup
         5: aload_0
         6: invokespecial #3                  // Method LambdaTest$1."<init>":(LLambdaTest;)V
         9: invokevirtual #4                  // Method invoke:(Ljava/lang/Runnable;)V
        12: goto          0

test2:
         0: new           #5                  // class LambdaTest$2
         3: dup
         4: aload_0
         5: invokespecial #6                  // Method LambdaTest$2."<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

Run Code Online (Sandbox Code Playgroud)

在 test1 中,每个循环都会创建匿名类的一个新实例并调用 init 方法。
在test2中,只有一个匿名类的实例,并且循环中的操作码数量最少。
在test3中,与test2唯一的区别是在循环中添加了一个invokedynamicbefore操作码。invokevirtual

根据本文,invokedynamic将第一次调用引导方法来创建匿名类的实例,之后它将在其余生中使用创建的实例。

所以我的建议是:只要你喜欢就使用 lambda,不必关心开销。