Java非静态内部类外部this对象和final类型详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/future234/article/details/80764123

1. 非静态内部类是如何引用外部类this对象的

Java内部类分为静态内部类和非静态内部类。它们有一个比较大的区别在于,非静态内部类拥有外部类的this对象的引用,从而使得非静态内部类可以访问外部类的成员函数,成员变量。这个结论我们大家都比较清楚,那么原理大家都懂吗?这篇文章我讲通过反编译的方法一探其中的奥秘

public class OuterClass {
    public void test() {
        System.out.println("test");
    }

    class InnerClass {
        public void test() {
            OuterClass.this.test();
        }
    }

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        innerClass.test();
    }
}

InnerClass的test()方法通过OuterClass.this对象直接调用外部类的test()方法。那么OuterClass.this对象到底是什么,它又是怎么初始化到InnerClass对象里的呢。下面通过查看class字节码指令来一探究竟

在classes目录中我们可以看到分别生成了两个class文件 分别为 OuterClass I n n e r C l a s s . c l a s s O u t e r C l a s s . c l a s s j a v a p c O u t e r C l a s s InnerClass.class

Compiled from "OuterClass.java"
class com.peter.tips.collections.OuterClass$InnerClass {
  final com.peter.tips.collections.OuterClass this$0;

  com.peter.tips.collections.OuterClass$InnerClass(com.peter.tips.collections.OuterClass);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lcom/peter/tips/collections/OuterClass;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void test();
    Code:
       0: aload_0
       1: getfield      #1                  // Field this$0:Lcom/peter/tips/collections/OuterClass;
       4: pop
       5: invokestatic  #3                  // Method com/peter/tips/collections/OuterClass.test:()V
       8: return
}

通过反编译InnerClass的字节码。我们发现InnerClass多了一个成员变量this 0 1. t h i s 0是如何被赋值的? 2.this$0是否就是OuterClass.this对象呢?

1. this$0赋值过程

  1. 我们来观察下InnerClass的构造函数OuterClass$InnerClass(com.peter.tips.collections.OuterClass)。字节码
//获取构造函数的参数OuterClass
1: aload_1

//赋值给this$0对象
2: putfield #1   // Field this$0:Lcom/peter/tips/collections/OuterClass;

正是通过传入的OuterClass对象给this$0赋值。

  1. 反编译OuterClass的main方法,我们看看outerClass.new InnerClass()这行代码做了什么
  public static void main(java.lang.String[]);
    Code:
       //创建OuterClass对象
       0: new           #5                  // class com/peter/tips/collections/OuterClass

       //对象再次压入栈
       3: dup

       //初始化OuterClass
       4: invokespecial #6                  // Method "<init>":()V

       //OuterClass对象赋值给outerClass变量
       7: astore_1

       //创建InnerClass对象
       8: new           #7                  // class com/peter/tips/collections/OuterClass$InnerClass

       //对象再次压入栈
      11: dup

      //outClass对象压入栈
      12: aload_1

      //outClass对象压入栈
      13: dup

      //调用getClass()方法
      14: invokevirtual #8                  // Method java/lang/Object.getClass:()Ljava/lang/Class;

      //出栈
      17: pop

      //初始化InnerClass 相当于 new InnerClass(outerClass)
      18: invokespecial #9                  // Method com/peter/tips/collections/OuterClass$InnerClass."<init>":(Lcom/peter/tips/collections/OuterClass;)V

      //把创建的InnerClass对象赋值给innerClass变量 innerClass = new InnerClass(outerClass)
      21: astore_2

      //innerClass对象入栈
      22: aload_2

      //调用innerClass的test()方法
      23: invokevirtual #10                 // Method com/peter/tips/collections/OuterClass$InnerClass.test:()V
      26: return

综上,我们知道创建内部类对象,下面两个代码块是等价的

OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
OuterClass outerClass = new OuterClass();
InnerClass innerClass = new InnerClass(outerClass);

2. this$0是否就是OuterClass.this对象

前面可以知道this$0对象其实就是新建的OuterClass对象,大胆的猜测下结果,因为代码 只创建了一个OuterClass对象,他们指向的肯定是同一个对象了。通过查看InnerClass的test()字节码也可以佐证这个结论

InnerClass
public void test();
    Code:
        //InnerClass对象入栈
       0: aload_0

       //获取到this$0对象
       1: getfield      #1                  // Field this$0:Lcom/peter/tips/collections/OuterClass;

       //调用this$0的test()方法 == OuterClass.this.test()
       4: invokevirtual #3                  // Method com/peter/tips/collections/OuterClass.test:()V
       7: return

当然我们还可以通过反射来证明OuterClass.this、this$0的存在

System.out.println(innerClass.getClass().getDeclaredField("this$0"));

final com.peter.tips.nest.OuterClass com.peter.tips.nest.OuterClass$InnerClass.this$0

2. 匿名内部类使用外部参数为什么要用final

我们都知道如果在方法内创建匿名内部类,如果在匿名内部类中使用了方法的参数,或者局部变量。它们需要被定义成final类型。这是为什么呢?我们来看以下代码

public class Anonymous {
    public void test(final int i,final String str){
        String  j ="hello world";
        new InnerClass(){
            @Override
            void run() {
                System.out.println("j="+j+";i="+i+";str="+str);
            }
        }.run();
//        j="Hi world";
    }
    public static void main(String[] args) {
        Anonymous anonymous = new Anonymous();
        anonymous.test(1,"hello");
    }
    class InnerClass{
        void run(){

        }
    }
}

前面OuterClass的InnerClass会默认创建一个this 0 A n o n y m o u s 3 A n o n y m o u s 1.class、Anonymous I n n e r C l a s s . c l a s s A n o n y m o u s . c l a s s A n o n y m o u s 1.class正是Anonymous的test()方法中创建的匿名内部类对象。

Compiled from "Anonymous.java"
class com.peter.tips.nest.Anonymous$1 extends com.peter.tips.nest.Anonymous$InnerClass {
  final java.lang.String val$j;

  final int val$i;

  final java.lang.String val$str;

  final com.peter.tips.nest.Anonymous this$0;

  com.peter.tips.nest.Anonymous$1(com.peter.tips.nest.Anonymous, java.lang.String, int, java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lcom/peter/tips/nest/Anonymous;
       5: aload_0
       6: aload_2
       7: putfield      #2                  // Field val$j:Ljava/lang/String;
      10: aload_0
      11: iload_3
      12: putfield      #3                  // Field val$i:I
      15: aload_0
      16: aload         4
      18: putfield      #4                  // Field val$str:Ljava/lang/String;
      21: aload_0
      22: aload_1
      23: invokespecial #5                  // Method com/peter/tips/nest/Anonymous$InnerClass."<init>":(Lcom/peter/tips/nest/Anonymous;)V
      26: return

  void run();
    Code:
       0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #7                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #9                  // String j=
      12: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_0
      16: getfield      #2                  // Field val$j:Ljava/lang/String;
      19: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: ldc           #11                 // String ;i=
      24: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      27: aload_0
      28: getfield      #3                  // Field val$i:I
      31: invokevirtual #12                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      34: ldc           #13                 // String ;str=
      36: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      39: aload_0
      40: getfield      #4                  // Field val$str:Ljava/lang/String;
      43: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      46: invokevirtual #14                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      49: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      52: return
}

通过查看字节码我们可以看到,不管是形参还是局部变量,最终都被传到构造函数的形参里去了。而且Anonymous$1成员变量定义的都是final类型。所以外部参数也需要是final的才行

3. 非静态内部类是如何导致内存泄漏的

public class MemoryLeak {

    public static void main(String[] args) {
        MemoryLeak memoryLeak = new MemoryLeak();
        InnerClass innerClass = memoryLeak.new InnerClass();
        memoryLeak = null;
        System.gc();
    }
    class InnerClass {
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize");
    }
}

我们预期的程序将会打印”finalize”。但是并没有。原因是内部类对象持有了外部类对象的引用导致无法会回收

猜你喜欢

转载自blog.csdn.net/future234/article/details/80764123