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

分四种情况讨论:

普通内部类

public class Demo {
    public class DemoRunnable implements Runnable {
        @Override
        public void run() {
            
        }
    }
}

用javac命令生成字节码文件,根目录下生成两个文件Demo$DemoRunnable.classDemo.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.classDemo.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.classDemo.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.classDemo.class,查看反编译后的代码:

class Demo$1 implements Runnable {
    Demo$1(Demo var1) {
        this.this$0 = var1;
    }

    public void run() {
    }
}

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

故而,问题得解。

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

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

微信公众号 长夜西风

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

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值