三、内部类与外部类的引用关系

内部类引用外部类成员的情况

Java的内部类持有外部类的的引用,在字节码层面是怎么设计的呢?我们写一段代码来看看,假设有这么一个内部类Inner,访问了外部类Out,示例代码如下:

public class Outer {
	int outerField = 5;
	class Inner{
		public int InnerMethod(){
			//调用外部类的成员变量
			return outerField+1;
		}
	}
	public Inner getInnter(){
		return new Inner();
	}

	public static void main(String[] args) {
		Outer outer = new Outer();
		Inner innter = outer.getInnter();
		System.out.println(innter.InnerMethod());
	}
}

接下来我们使用:

javac Outer.java
java Outer  

这段代码的意思是,内部类访问了外部类的outerField,然后+1返回。最后再打印,我们贴一下打印结果:

6

接下来我们看一下我们Javac之后,反编译的产物:

Outer.class

public class Outer {
    int outerField = 5;

    public Outer() {
    }

    public Outer.Inner getInnter() {
        return new Outer.Inner();
    }

    public static void main(String[] var0) {
        Outer var1 = new Outer();
        Outer.Inner var2 = var1.getInnter();
        System.out.println(var2.InnerMethod());
    }

    class Inner {
        Inner() {
        }

        public int InnerMethod() {
            return Outer.this.outerField + 1;
        }
    }
}

Outer$Inner.class

class Outer$Inner {
    Outer$Inner(Outer var1) {
        this.this$0 = var1;
    }

    public int InnerMethod() {
        return this.this$0.outerField + 1;
    }
}

可以看到,Out$Innter.class反编译后的构造函数默认带了Outer参数。

接下来我们使用javap -c Out来看看字节码

Compiled from "Outer.java"
public class com.java.oom.Outer {
  int outerField;

  //out class
  public com.java.oom.Outer();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_5
       6: putfield      #7                  // Field outerField:I
       9: return
     
  public com.java.oom.Outer$Inner getInnter();
    Code:
       0: new           #13                 // class com/java/oom/Outer$Inner
       3: dup
       4: aload_0
       //这行, invokespecial需要传入 (Lcom/java/oom/Outer;)V来执行init函数。
       5: invokespecial #15                 // Method com/java/oom/Outer$Inner."<init>":(Lcom/java/oom/Outer;)V
       8: areturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #8                  // class com/java/oom/Outer
       3: dup
       4: invokespecial #18                 // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #19                 // Method getInnter:()Lcom/java/oom/Outer$Inner;
      12: astore_2
      13: getstatic     #23                 // Field java/lang/System.out:Ljava/io/PrintStream;
      16: aload_2
      17: invokevirtual #29                 // Method com/java/oom/Outer$Inner.InnerMethod:()I
      20: invokevirtual #33                 // Method java/io/PrintStream.println:(I)V
      23: return
}

综合反编译的结果和字节码,所以我们说,Java内部类会在init阶段,把外部类的对象传进去,然后在构造函数里面赋值为内部类的成员变量。

内部类不使用外部类的成员,是否会持有外部类的对象

那我们看一下,假设内部类不引用外部类的成员变量,会是什么样呢?

我们对刚才的Outer进行小范围的修改:

package com.java.oom;

import java.util.concurrent.atomic.AtomicInteger;

public class Outer {
	class Inner{
		public int InnerMethod(){
			//调用外部类的成员变量
			return 10;
		}
	}
	public Inner getInnter(){
		return new Inner();
	}

	public static void main(String[] args) {
		Outer outer = new Outer();
		Inner innter = outer.getInnter();
		System.out.println(innter.InnerMethod());
	}
}

接下来我们编译一下

javac Outer.java
java Outer  

执行结果:

10

看一下生成的Outer.class:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.java.oom;

public class Outer {
    public Outer() {
    }

    public Outer.Inner getInnter() {
        return new Outer.Inner();
    }

    public static void main(String[] var0) {
        Outer var1 = new Outer();
        Outer.Inner var2 = var1.getInnter();
        System.out.println(var2.InnerMethod());
    }

    class Inner {
        Inner() {
        }

        public int InnerMethod() {
            return 10;
        }
    }
}

然后看一下Inner.class:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.java.oom;

class Outer$Inner {
    Outer$Inner(Outer var1) {
        this.this$0 = var1;
    }

    public int InnerMethod() {
        return 10;
    }
}

这里看到还是有引用的,但是反编译出来的不一定是最终的,我们来看看字节码:

警告: 二进制文件Outer包含com.java.oom.Outer
Compiled from "Outer.java"
public class com.java.oom.Outer {
  public com.java.oom.Outer();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public com.java.oom.Outer$Inner getInnter();
    Code:
       0: new           #2                  // class com/java/oom/Outer$Inner
       3: dup
       4: aload_0
       5: invokespecial #3                  // Method com/java/oom/Outer$Inner."<init>":(Lcom/java/oom/Outer;)V
       8: areturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #4                  // class com/java/oom/Outer
       3: dup
       4: invokespecial #5                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #6                  // Method getInnter:()Lcom/java/oom/Outer$Inner;
      12: astore_2
      13: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: aload_2
      17: invokevirtual #8                  // Method com/java/oom/Outer$Inner.InnerMethod:()I
      20: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
      23: return
}

我们看看getInner的第五航,这里调用了Inner方法的init方法,这里你可以认为是调用了构造函数,传入的参数是

com/java/oom/Outer;返回值是void。到这里就真的确定在new Inner的时候,传入了Outer对象。那证明内部类无论是使用还是不适用外部类的对象,均会持有外部类的引用。所以开发时需要谨慎的使用匿名内部类,控制好使用的生命周期。还有就是使用静态内部类。都说到这了,我们看一下静态内部类的字节码表现是怎么样的.

静态内部类

我们对之前的类进行稍微的修改,把inner变成static:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.java.oom;

public class Outer {
    public Outer() {
    }

    public Outer.Inner getInnter() {
        return new Outer.Inner();
    }

    public static void main(String[] var0) {
        Outer var1 = new Outer();
        Outer.Inner var2 = var1.getInnter();
        System.out.println(var2.InnerMethod());
    }

    static class Inner {
        Inner() {
        }

        public int InnerMethod() {
            return 10;
        }
    }
}




package com.java.oom;

class Outer$Inner {
    Outer$Inner() {
    }

    public int InnerMethod() {
        return 10;
    }
}


我们再来看一下字节码:

警告: 二进制文件Outer包含com.java.oom.Outer
Compiled from "Outer.java"
public class com.java.oom.Outer {
  public com.java.oom.Outer();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public com.java.oom.Outer$Inner getInnter();
    Code:
       0: new           #2                  // class com/java/oom/Outer$Inner
       3: dup
       4: invokespecial #3                  // Method com/java/oom/Outer$Inner."<init>":()V
       7: areturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #4                  // class com/java/oom/Outer
       3: dup
       4: invokespecial #5                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #6                  // Method getInnter:()Lcom/java/oom/Outer$Inner;
      12: astore_2
      13: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: aload_2
      17: invokevirtual #8                  // Method com/java/oom/Outer$Inner.InnerMethod:()I
      20: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
      23: return
}

从字节码可以看出,调用getInner方法的invokespecial函数,执行Init的参数和返回值是

"<init>":()V //()表示没有参数

这里非常需要注意的一个点是,静态内部类的生命周期是很长的,如果持有了短生命周期的东西,会造成内存泄漏,通常的解法是使用弱引用来持有短生命周期的对象。以AndroidHandler为例子:

package com.jhzl.memoryleak;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {

   static class MainHandler extends  Handler{
       WeakReference<Activity> activityWeakReference = null;
       MainHandler(Activity activity){
            activityWeakReference = new WeakReference(activity);
       }
       @Override
       public void handleMessage(@NonNull Message msg) {
           super.handleMessage(msg);
           if (activityWeakReference == null){
               return;
           }
           Activity activity = activityWeakReference.get();
       }
   }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MainHandler mainHandler = new MainHandler(this);
    }
}

这里就使用到了Activity对象,有可能生命周期就比Handler短,所以需要用弱引用包裹一下,防止泄露。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值