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对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
9天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
33 17
|
8天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
17天前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第7天】Java零基础教学篇,手把手实践教学!
23 6
|
18天前
|
Java 程序员
Java 面试高频考点:static 和 final 深度剖析
本文介绍了 Java 中的 `static` 和 `final` 关键字。`static` 修饰的属性和方法属于类而非对象,所有实例共享;`final` 用于变量、方法和类,确保其不可修改或继承。两者结合可用于定义常量。文章通过具体示例详细解析了它们的用法和应用场景。
22 3
|
16天前
|
存储 安全 Java
了解final关键字在Java并发编程领域的作用吗?
在Java并发编程中,`final`关键字不仅用于修饰变量、方法和类,还在多线程环境中确保对象状态的可见性和不变性。本文深入探讨了`final`关键字的作用,特别是其在final域重排序规则中的应用,以及如何防止对象的“部分创建”问题,确保线程安全。通过具体示例,文章详细解析了final域的写入和读取操作的重排序规则,以及这些规则在不同处理器上的实现差异。
了解final关键字在Java并发编程领域的作用吗?
|
21天前
|
Java 编译器
Java“返回类型为 void 的方法不能返回一个值”解决
在 Java 中,如果一个方法的返回类型被声明为 void,那么该方法不应该包含返回值的语句。如果尝试从这样的方法中返回一个值,编译器将报错。解决办法是移除返回值语句或更改方法的返回类型。
|
20天前
|
Java
Java 中锁的主要类型
【10月更文挑战第10天】
|
21天前
|
Oracle Java 关系型数据库
重新定义 Java 对象相等性
本文探讨了Java中的对象相等性问题,包括自反性、对称性、传递性和一致性等原则,并通过LaptopCharger类的例子展示了引用相等与内容相等的区别。文章还介绍了如何通过重写`equals`方法和使用`Comparator`接口来实现更复杂的相等度量,以满足特定的业务需求。
16 3
|
8天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
7 0