分四种情况讨论:
普通内部类
public class Demo {
public class DemoRunnable implements Runnable {
@Override
public void run() {
}
}
}
用javac命令生成字节码文件,根目录下生成两个文件Demo$DemoRunnable.class
和 Demo.class
,查看反编译后的代码,
public class Demo$DemoRunnable implements Runnable {
public Demo$DemoRunnable(Demo var1) {
this.this$0 = var1;
}
public void run() {
}
}
发现生成的类只有一个构造器,参数就是Demo类型,而且保存到内部类本身的this$0字段中。
接下来修改DemoRunnable
的代码:
public class Demo {
public class DemoRunnable implements Runnable {
@Override
public void run() {
}
}
public void run() {
DemoRunnable demoRunnable = new DemoRunnable();
demoRunnable.run();
}
}
再次执行javac,并用javap命令查看生成的字节码:
Classfile /E:/repos/java_project/test_project/src/com/cmder/Demo.class
Last modified 2020年8月19日; 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对象作为了参数传递进来
结论:普通内部类会持有外部类的引用
以下的分析方法和上面类似,这里给出简化过程。
匿名内部类
public class Demo {
private Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
}
根目录下生成两个文件Demo$1.class
和 Demo.class
,查看反编译后的代码:
class Demo$1 implements Runnable {
Demo$1(Demo var1) {
this.this$0 = var1;
}
public void run() {
}
}
结论:匿名内部类会持有外部类的引用
局部内部类
public class Demo {
public void work() {
class InnerRunnable implements Runnable {
@Override
public void run() {
}
}
InnerRunnable runnable = new InnerRunnable();
}
}
根目录下生成两个文件Demo$1InnerRunnable.class
和 Demo.class
,查看反编译后的代码:
class Demo$1InnerRunnable implements Runnable {
Demo$1InnerRunnable(Demo var1) {
this.this$0 = var1;
}
public void run() {
}
}
结论:局部内部类会持有外部类的引用
局部匿名内部类
public class Demo {
public void work() {
Runnable runnable = new Runnable(){
@Override
public void run() {
}
};
}
}
根目录下生成两个文件Demo$1.class
和 Demo.class
,查看反编译后的代码:
class Demo$1 implements Runnable {
Demo$1(Demo var1) {
this.this$0 = var1;
}
public void run() {
}
}
结论:局部匿名内部类会持有外部类的引用
故而,问题得解。
如果想通过我的文字更加深刻地了解这个世界,那就关注我的公众号吧!
微信内长按或用微信扫描下方的二维码即可。
微信公众号 长夜西风
个人网站 http://www.cmder.info/