Lambda表达式与匿名内部类的内部实现区别

在Java8提供Lambda表达式后,有些地方可以用比较简洁的Lambda表达式来代替原来相对冗余的匿名内部类了。那么Java在低层实现时,这2种方式有什么区别呢,下面通过例子来分析一下。

1、简单实例

下面列出一个简单的类,类中提供了2 个方式,一个方式用匿名内部类的方式实现;另一个用Lambda表达式的方式实现,即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package io.github.lzj09.java8.lambda;

public class LambdaDemo1 {

public static void main(String[] args) {
LambdaDemo1 demo = new LambdaDemo1();

demo.anonymousClassMethod();
demo.lambdaFunctionMethod();
}

public void anonymousClassMethod() {
new Thread(new Runnable() {

@Override
public void run() {
System.out.println("匿名内部类方式执行...");
}

}).start();
}

public void lambdaFunctionMethod() {
new Thread(() -> System.out.println("lambda函数方式执行...")).start();
}
}

程序很简单,输出的结果为:

1
2
匿名内部类方式执行...
lambda函数方式执行...

2、分析生成的对应的class字节码文件

查看classes文件夹中的字节码文件中,会发现有2个class文件,即LambdaDemo1.class和LambdaDemo1$1.class,如图:

利用javap命令查看这2个class文件对应的汇编指令,首先看看LambdaDemo1$1.class文件,即输入如下命令:

1
javap -c -p LambdaDemo1$1.class

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Compiled from "LambdaDemo1.java"
class io.github.lzj09.java8.lambda.LambdaDemo1$1 implements java.lang.Runnable {
final io.github.lzj09.java8.lambda.LambdaDemo1 this$0;

io.github.lzj09.java8.lambda.LambdaDemo1$1(io.github.lzj09.java8.lambda.LambdaDemo1);
Code:
0: aload_0
1: aload_1
2: putfield #12 // Field this$0:Lio/github/lzj09/java8/lambda/LambdaDemo1;
5: aload_0
6: invokespecial #14 // Method java/lang/Object."<init>":()V
9: return

public void run();
Code:
0: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #28 // String 匿名内部类方式执行...
5: invokevirtual #30 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}

从上面的汇编指令大致可以看到LambdaDemo1$1类实现了Runnable接口,并实现run()方法,同时该方法输入一个字符串,即“匿名内部类方式执行…”。

从上可以看出匿名内部类在编译时,会生成一个类并实现Runnable接口同时实现run()方法。

再利用javap命令查看LambdaDemo1.class文件,即:

1
javap -c -p LambdaDemo1.class

结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Compiled from "LambdaDemo1.java"
public class io.github.lzj09.java8.lambda.LambdaDemo1 {
public io.github.lzj09.java8.lambda.LambdaDemo1();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: new #1 // class io/github/lzj09/java8/lambda/LambdaDemo1
3: dup
4: invokespecial #16 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #17 // Method anonymousClassMethod:()V
12: aload_1
13: invokevirtual #20 // Method lambdaFunctionMethod:()V
16: return

public void anonymousClassMethod();
Code:
0: new #26 // class java/lang/Thread
3: dup
4: new #28 // class io/github/lzj09/java8/lambda/LambdaDemo1$1
7: dup
8: aload_0
9: invokespecial #30 // Method io/github/lzj09/java8/lambda/LambdaDemo1$1."<init>":(Lio/github/lzj09/java8/lambda/LambdaDemo1;)V
12: invokespecial #33 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
15: invokevirtual #36 // Method java/lang/Thread.start:()V
18: return

public void lambdaFunctionMethod();
Code:
0: new #26 // class java/lang/Thread
3: dup
4: invokedynamic #42, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
9: invokespecial #33 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
12: invokevirtual #36 // Method java/lang/Thread.start:()V
15: return

private static void lambda$0();
Code:
0: getstatic #44 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #50 // String lambda函数方式执行...
5: invokevirtual #52 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}

从上可以发现,多了一个方法lambda$0(),而该方法内容刚好是Lambda表达式中的内容,即输出“lambda函数方式执行…”。到这其实会有一个疑问,这个lambda$0()方法,在哪里被调用呢?这时需要利用java命令执行时,加入-Djdk.internal.lambda.dumpProxyClasses参数,这样才能真正看到lambda$0()方法调用过程,即:

1
java -Djdk.internal.lambda.dumpProxyClasses io.github.lzj09.java8.lambda.LambdaDemo1

执行该命令后,同样是会输出结果,即:

1
2
匿名内部类方式执行...
lambda函数方式执行...

但是查看classes文件,会发现多了一个class文件LambdaDemo1$$Lambda$1.class,如图:

同样用javap命令查看LambdaDemo1$$Lambda$1.class的汇编指令,即:

1
javap -c -p LambdaDemo1$$Lambda$1.class

得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
final class io.github.lzj09.java8.lambda.LambdaDemo1$$Lambda$1 implements java.lang.Runnable {
private io.github.lzj09.java8.lambda.LambdaDemo1$$Lambda$1();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return

public void run();
Code:
0: invokestatic #17 // Method io/github/lzj09/java8/lambda/LambdaDemo1.lambda$0:()V
3: return
}

可以发现类LambdaDemo1$$Lambda$1实现了Runnable接口,并实现run()方法,同时该方法中就是调用LambdaDemo1类中编译时生成的lambda$0()方法的。

3、总结

Lambda表达式与匿名内部类的低层实现过程还是有些区别的:

  • 匿名内部类

    是在编译时,新增一个class,同时该class中实现相应的接口和方法。

  • Lambda表达式

    1. 编译时会先新增一个方法,该方法内容即为Lambda表达式中的内容.
    2. 运行时会新增一个class,同时该class中实现相应的接口和方法,但方法中是调用第1步中生成的方法。

Lambda表达式与匿名内部类的内部实现区别

https://lzj09.github.io/2021/05/15/java8-lambda-vs-anonymous/

作者

lzj09

发布于

2021-05-15

更新于

2021-05-15

许可协议

评论