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

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

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文件 分别为 OuterClassInnerClass.classOuterClass.class。javap−cOuterClassInnerClass.classOuterClass.class。javap−cOuterClassInnerClass.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多了一个成员变量this0。这里有两个问题1.this0。这里有两个问题1.this0是如何被赋值的? 2.this$0是否就是OuterClass.this对象呢?


1. this$0赋值过程



我们来观察下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会默认创建一个this0成员变量。我们来看下Anonymous反编译的情况。反编译查看生成了3个文件Anonymous0成员变量。我们来看下Anonymous反编译的情况。反编译查看生成了3个文件Anonymous1.class、AnonymousInnerClass.class和Anonymous.class。AnonymousInnerClass.class和Anonymous.class。Anonymous1.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”。但是并没有。原因是内部类对象持有了外部类对象的引用导致无法会回收

相关文章
|
5天前
|
缓存 前端开发 Java
【前端学java】复习巩固-Java中的对象比较(15)
【8月更文挑战第11天】Java中的对象比较
15 1
【前端学java】复习巩固-Java中的对象比较(15)
|
3天前
|
Java API 开发者
|
7天前
|
前端开发 JavaScript Java
【前端学java】java中final修饰符(6)
【8月更文挑战第9天】java中final修饰符(6)
17 2
|
7天前
|
Java Android开发
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
25 1
|
11天前
|
算法 Java
12 Java常用类(一)(内部类+object类+包装类)
12 Java常用类(一)(内部类+object类+包装类)
25 5
|
2天前
|
存储 设计模式 Java
在 Java 中创建多个对象
【8月更文挑战第17天】
6 0
|
5天前
|
Java Spring 容器
Java SpringBoot 中,动态执行 bean 对象中的方法
Java SpringBoot 中,动态执行 bean 对象中的方法
14 0
|
5天前
|
Java
Java Bean 注册对象
Java Bean 注册对象
8 0
|
6天前
|
Java
【Java】内部类、枚举、泛型
【Java】内部类、枚举、泛型
|
11天前
|
设计模式 存储 安全
18 Java反射reflect(类加载+获取类对象+通用操作+设计模式+枚举+注解)
18 Java反射reflect(类加载+获取类对象+通用操作+设计模式+枚举+注解)
40 0