Java局部匿名内部类是否会持有外部类的引用?

分四种情况讨论:

普通内部类

1
2
3
4
5
6
7
8
public class Demo {
public class DemoRunnable implements Runnable {
@Override
public void run() {

}
}
}

用javac命令生成字节码文件,根目录下生成两个文件Demo$DemoRunnable.classDemo.class,查看反编译后的代码,

1
2
3
4
5
6
7
8
public class Demo$DemoRunnable implements Runnable {
public Demo$DemoRunnable(Demo var1) {
this.this$0 = var1;
}

public void run() {
}
}

发现生成的类只有一个构造器,参数就是Demo类型,而且保存到内部类本身的this$0字段中。

接下来修改DemoRunnable的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {
public class DemoRunnable implements Runnable {
@Override
public void run() {

}
}

public void run() {
DemoRunnable demoRunnable = new DemoRunnable();
demoRunnable.run();
}
}

再次执行javac,并用javap命令查看生成的字节码:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Classfile /E:/repos/java_project/test_project/src/com/cmder/Demo.class
Last modified 2020819日; size 403 bytes
MD5 checksum dc1e9ef17109bd89a91205c798879493
Compiled from "Demo.java"
public class com.cmder.Demo
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5 // com/cmder/Demo
super_class: #6 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
Constant pool:
#1 = Methodref #6.#17 // java/lang/Object."<init>":()V
#2 = Class #18 // com/cmder/Demo$DemoRunnable
#3 = Methodref #2.#19 // com/cmder/Demo$DemoRunnable."<init>":(Lcom/cmder/Demo;)V
#4 = Methodref #2.#20 // com/cmder/Demo$DemoRunnable.run:()V
#5 = Class #21 // com/cmder/Demo
#6 = Class #22 // java/lang/Object
#7 = Utf8 DemoRunnable
#8 = Utf8 InnerClasses
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 run
#14 = Utf8 SourceFile
#15 = Utf8 Demo.java
#16 = Utf8 NestMembers
#17 = NameAndType #9:#10 // "<init>":()V
#18 = Utf8 com/cmder/Demo$DemoRunnable
#19 = NameAndType #9:#23 // "<init>":(Lcom/cmder/Demo;)V
#20 = NameAndType #13:#10 // run:()V
#21 = Utf8 com/cmder/Demo
#22 = Utf8 java/lang/Object
#23 = Utf8 (Lcom/cmder/Demo;)V
{
public com.cmder.Demo();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0

public void run();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class com/cmder/Demo$DemoRunnable
3: dup
4: aload_0
5: invokespecial #3 // Method com/cmder/Demo$DemoRunnable."<init>":(Lcom/cmder/Demo;)V
8: astore_1
9: aload_1
10: invokevirtual #4 // Method com/cmder/Demo$DemoRunnable.run:()V
13: return
LineNumberTable:
line 12: 0
line 13: 9
line 14: 13
}
SourceFile: "Demo.java"
NestMembers:
com/cmder/Demo$DemoRunnable
InnerClasses:
public #7= #2 of #5; // DemoRunnable=class com/cmder/Demo$DemoRunnable of class com/cmder/Demo

研究run()方法在字节码中的描述:

0:新建一个Demo$DemoRunnable对象

4:aload_0指令将外部类Demo的this对象压栈

5:调用Demo$DemoRunnable类的init方法(即参数为Demo的构造方法),将Demo对象作为了参数传递进来

结论:普通内部类会持有外部类的引用

以下的分析方法和上面类似,这里给出简化过程。

匿名内部类

1
2
3
4
5
6
7
8
public class Demo {
private Runnable runnable = new Runnable() {
@Override
public void run() {

}
};
}

根目录下生成两个文件Demo$1.classDemo.class,查看反编译后的代码:

1
2
3
4
5
6
7
8
class Demo$1 implements Runnable {
Demo$1(Demo var1) {
this.this$0 = var1;
}

public void run() {
}
}

结论:匿名内部类会持有外部类的引用

局部内部类

1
2
3
4
5
6
7
8
9
10
11
public class Demo {
public void work() {
class InnerRunnable implements Runnable {
@Override
public void run() {

}
}
InnerRunnable runnable = new InnerRunnable();
}
}

根目录下生成两个文件Demo$1InnerRunnable.classDemo.class,查看反编译后的代码:

1
2
3
4
5
6
7
8
class Demo$1InnerRunnable implements Runnable {
Demo$1InnerRunnable(Demo var1) {
this.this$0 = var1;
}

public void run() {
}
}

结论:局部内部类会持有外部类的引用

局部匿名内部类

1
2
3
4
5
6
7
8
9
10
public class Demo {
public void work() {
Runnable runnable = new Runnable(){
@Override
public void run() {

}
};
}
}

根目录下生成两个文件Demo$1.classDemo.class,查看反编译后的代码:

1
2
3
4
5
6
7
8
class Demo$1 implements Runnable {
Demo$1(Demo var1) {
this.this$0 = var1;
}

public void run() {
}
}

结论:局部匿名内部类会持有外部类的引用

故而,问题得解。

如果想通过我的文字更加深刻地了解这个世界,那就关注我的公众号吧!

微信内长按或用微信扫描下方的二维码即可。

微信公众号 长夜西风

个人网站 http://www.cmder.info/

书痴者文必工,艺痴者技必良。