java中内部类为什么会持有外部类的引用?

当我们分析内存泄漏的场景时,总会想到不能在内部类中做耗时操作,因为它会持有外部类的因为,导致外部类的实例在生命周期结束的时候没有办法及时释放,这就造成了内存泄漏.

好像这就是一个公理一样,就是人们说着说着就都认可它了,却没有人能说出个为什么.

今天我们就来分析一下为什么吧

首先来看一个例子

public class Outer {

    private int count;

    //匿名内部类1
    private StaticInner si1 = new StaticInner(){
        @Override
        public void doAction() {
            count++;

        }
    };

    private StaticInner si2;

    private Inner i3;


    public void setInner(StaticInner inner) {
        this.si2 = inner;
    }


    public Outer() {
        i3 = new Inner();
        //匿名内部类2
        setInner(new StaticInner(){
            @Override
            public void doAction() {
                super.doAction();
                count++;
            }
        });
    }

    public void doAction() {
        si1.doAction();
        si2.doAction();
        i3.doSomething();
    }


    /**
     * 内部类
     */
    private class Inner {

        public void doSomething() {
            count++;
        }
    }


    /**
     * 静态内部类
     */
    private static class StaticInner{

        public void doAction() {

        }
    }
}

以上代码概述了我们平时写代码时常见的几种内部类和匿名内部类以及静态内部类的写法,并访问了外部类的成员变量,当然静态内部类没法访问,编译器会报错.

然后通过javac命令编译一下.java源文件,得到几个.class字节码文件,这时如果你尝试打开字节码文件,会发现一堆乱码,可以用jd-gui字节码反编译工具打开.

我们可以发现好几个.class文件,一个一个来看

Outer$1.class

class Outer$1
  extends Outer.StaticInner
{
  Outer$1(Outer paramOuter)
  {
    super(null);
  }
  
  public void doAction()
  {
    Outer.access$108(this.this$0);
  }
}

这个类代表我们声明成员变量si1的匿名内部类,可以看到它继承自静态内部类StaticInner,它还定义了一个构造函数,并传入了内部类的实例作为参数,虽然不知道super(null)具体实现,但是我们知道它要访问paramOuter实例的成员变量,必须得使用它的引用来访问,所以它肯定是持有了外部类实例的引用.

Outer$2.class

class Outer$2
  extends Outer.StaticInner
{
  Outer$2(Outer paramOuter)
  {
    super(null);
  }
  
  public void doAction()
  {
    super.doAction();
    Outer.access$108(this.this$0);
  }
}

这个和上面提到的Outer$1.class类似,所以这两种匿名内部类是没有任何本质区别的,不管是定义成员变量还是方法传参.

Outer$Inner.class

class Outer$Inner
{
  private Outer$Inner(Outer paramOuter) {}
  
  public void doSomething()
  {
    Outer.access$108(this.this$0);
  }
}

这是定义的内部类,可以看到编译器也为它单独生成了一个.class文件,构造函数被定义为私有的,且同样传入了外部类的实例,所以它也是持有了外部类实例的引用.

Outer$StaticInner.class

class Outer$StaticInner
{
  public void doAction() {}
}

这是静态内部类编译后的字节码文件,编译器并没有为它添加额外的构造函数,所以它其实和我们的外部类没有任何关系,这是写在同一个.java源文件中而已.

分析到这里,应该算可以证明为什么静态内部类会持有外部类的实例了

在分析内存泄漏的时候,它应该是我们应该重点关注的对象.

由此我们也能够明白java代码执行过程并不是我们所看的那样,编译器也做了很多工作.只是这一块我们并不怎么关注.

转载于:https://www.cnblogs.com/wenjianes/p/9957946.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值