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”。但是并没有。原因是内部类对象持有了外部类对象的引用导致无法会回收

相关文章
|
2月前
|
存储 缓存 安全
除了变量,final还能修饰哪些Java元素
在Java中,final关键字不仅可以修饰变量,还可以用于修饰类、方法和参数。修饰类时,该类不能被继承;修饰方法时,方法不能被重写;修饰参数时,参数在方法体内不能被修改。
39 2
|
12天前
|
Java
java代码优化:判断内聚到实体对象中和构造上下文对象传递参数
通过两个常见的java后端实例场景探讨代码优化,代码不是优化出来的,而是设计出来的,我们永远不可能有专门的时间去做代码优化,优化和设计在平时
29 15
|
2月前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
2月前
|
存储 Java 开发者
Java 中 Set 类型的使用方法
【10月更文挑战第30天】Java中的`Set`类型提供了丰富的操作方法来处理不重复的元素集合,开发者可以根据具体的需求选择合适的`Set`实现类,并灵活运用各种方法来实现对集合的操作和处理。
|
3月前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
73 17
|
2月前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
98 2
|
2月前
|
存储 Java 编译器
Java泛型类型擦除以及类型擦除带来的问题
泛型擦除是指Java编译器在编译期间会移除所有泛型信息,使所有泛型类型在运行时都变为原始类型。例如,`List&lt;String&gt;` 和 `List&lt;Integer&gt;` 在JVM中都视为 `List`。因此,通过 `getClass()` 比较两个不同泛型类型的 `ArrayList` 实例会返回 `true`。此外,通过反射调用 `add` 方法可以向 `ArrayList&lt;Integer&gt;` 中添加字符串,进一步证明了泛型信息在运行时被擦除。
59 2
|
2月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
2月前
|
Java
final 在 java 中有什么作用
在 Java 中,`final` 关键字用于限制变量、方法和类的修改或继承。对变量使用 `final` 可使其成为常量;对方法使用 `final` 禁止其被重写;对类使用 `final` 禁止其被继承。
45 0
|
3月前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第7天】Java零基础教学篇,手把手实践教学!
40 6