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

相关文章
|
10天前
|
Java
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
本文介绍了Java中抽象类和抽象方法的使用,以及ArrayList的基本操作,包括添加、获取、删除元素和判断列表是否为空。
13 2
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
|
10天前
|
存储 Java Windows
java基础(9)数据类型中的char类型以及涉及到的转义字符
Java中的char类型可以存储一个中文字符,因为它占用两个字节。转义字符允许在代码中使用特殊字符,例如`\n`表示换行,`\t`表示制表符,`\\`表示反斜杠,`\'`表示单引号,`\"`表示双引号。可以使用`\u`后跟Unicode编码来表示特定的字符。
25 2
java基础(9)数据类型中的char类型以及涉及到的转义字符
|
21天前
|
Java 编译器
Java——类与对象(继承和多态)
本文介绍了面向对象编程中的继承概念,包括如何避免重复代码、构造方法的调用规则、成员变量的访问以及权限修饰符的使用。文中详细解释了继承与组合的区别,并探讨了多态的概念,包括向上转型、向下转型和方法的重写。此外,还讨论了静态绑定和动态绑定的区别,以及多态带来的优势和弊端。
23 9
Java——类与对象(继承和多态)
|
21天前
|
SQL Java 编译器
Java——类与对象(封装)
封装是面向对象编程中的概念,指将数据(属性)和相关操作(方法)组合成独立单元(类),使外部无法直接访问对象的内部状态,只能通过提供的方法进行交互,从而保护数据安全。例如,手机将各种组件封装起来,只暴露必要的接口供外部使用。实现封装时,使用`private`关键字修饰成员变量,并提供`get`和`set`方法进行访问和修改。此外,介绍了包的概念、导入包的方式及其注意事项,以及`static`关键字的使用,包括静态变量和方法的初始化与代码块的加载顺序。
25 10
Java——类与对象(封装)
|
21天前
|
Java C语言
Java——类与对象
这段内容介绍了Java中的类和对象、`this`关键字及构造方法的基本概念。类是对现实世界事物的抽象描述,包含属性和方法;对象是类的实例,通过`new`关键字创建。`this`关键字用于区分成员变量和局部变量,构造方法用于初始化对象。此外,还介绍了标准JavaBean的要求和生成方法。
21 9
Java——类与对象
|
3天前
|
存储 安全 Java
Java 数据结构类型总结
在 Java 中,常用的数据结构包括基础数据结构(如数组和字符串)、集合框架(如 Set、List 和 Map 接口的多种实现)、特殊数据结构(如栈、队列和双端队列)、链表(单链表、双链表和循环链表)以及图和树等。这些数据结构各有特点和适用场景,选择时需考虑性能、内存和操作需求。集合框架提供了丰富的接口和类,便于处理对象集合。
|
11天前
|
存储 Java
Java编程中的对象和类
【8月更文挑战第55天】在Java的世界中,“对象”与“类”是构建一切的基础。就像乐高积木一样,类定义了形状和结构,而对象则是根据这些设计拼装出来的具体作品。本篇文章将通过一个简单的例子,展示如何从零开始创建一个类,并利用它来制作我们的第一个Java对象。准备好让你的编程之旅起飞了吗?让我们一起来探索这个神奇的过程!
25 10
|
10天前
|
Java
java基础(10)数据类型中的整数类型
Java中的整数类型包括byte、short、int和long。整数字面值默认为int类型,加L表示long类型。整数字面值可以是十进制、八进制(0开头)或十六进制(0x开头)。小容量类型(如int)可自动转换为大容量类型(如long),但大容量转小容量需强制转换,可能导致精度损失。
22 2
|
13天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
14天前
|
存储 Java 开发者
Java编程中的对象序列化与反序列化
【9月更文挑战第20天】在本文中,我们将探索Java编程中的一个核心概念——对象序列化与反序列化。通过简单易懂的语言和直观的代码示例,你将学会如何将对象状态保存为字节流,以及如何从字节流恢复对象状态。这不仅有助于理解Java中的I/O机制,还能提升你的数据持久化能力。准备好让你的Java技能更上一层楼了吗?让我们开始吧!
下一篇
无影云桌面