首页 > 解决方案 > 这是对在 Java 中创建新对象的过程的正确描述吗?

问题描述

一般来说,我是 Java 和 OOP 的新手。在无数次尝试弄清楚创建一个新对象的过程之后,我仍然怀疑我是否正确理解了里面到底发生了什么(比如'操作符的作用是什么new?','谁调用了构造函数?','如何构造函数是否知道要初始化什么对象?''是否this存在于所有阶段?'等)。

假设我们有一个代码:

class NewObject {
    private int varA;
    private int varB;

    public NewObject(int a, int b) {
        varA = a;
        varB = b;
    }
}

public class Test {
    public static void main(String args[]) {
        NewObject obj = new NewObject(3, 4);
    }
}

在舞台后面(我用斜体突出了那些让我最怀疑的地方):

  1. obj声明类的新引用变量NewObject
  2. 运算符向 Java 请求一些堆内存,以使用类的声明作为蓝图new来分配对象。NewObject
  3. 运算符new将Java提供的内存块的地址存储在变量内部obj
  4. new然后,运算符在该新创建的对象中调用构造函数,并传递两个数值(显式地为 3 和 4)以及该对象的地址(this隐式地)作为参数。
  5. 在对象内部,构造函数创建两个局部变量ab并为它们分配从new. 构造函数还隐式创建了一个本地this来存储对象的地址。
  6. 构造函数在它的主体中看到varAvarB,但没有看到显式this附加到它们,因此它首先将它们视为局部变量。由于找不到这些局部变量的对应声明,所以它认为它们一定是实例变量。
  7. 构造函数因此搜索implicit this,当它找到时this,它使用它的值作为一个对象的引用(地址),该对象的实例变量必须使用其局部变量的值进行初始化

这是正确的还是我错过了什么?谢谢!

标签: javaconstructornew-operator

解决方案


让我们看一下您的 main 方法的反编译字节码(我省略了一些不太相关的部分):

  public static void main(java.lang.String[]);
    Code:
      stack=4, locals=2, args_size=1
         0: new           #2                  // class NewObject
         3: dup
         4: iconst_3
         5: iconst_4
         6: invokespecial #3                  // Method NewObject."<init>":(II)V
         9: astore_1
        10: return
  • #0 说“分配一个由 #2 引用的类型的新对象(它旁边的注释很好地告诉我们是类NewObject
  • #3 说“复制堆栈上的最新值”(恰好是对新分配对象的引用)。现在堆栈包含对新对象的 2 个引用。
  • #4 和 #5 将数字 3 和 4 放入堆栈
  • #6 通过引用 #3 调用构造函数,使用invokespecial. 这将从堆栈中获取所需的参数并将它们从堆栈中弹出(堆栈上的最后 3 个值是对新对象的引用以及数字 3 和 4)
  • #9 将堆栈中的剩余值存储在局部变量 #1 中(这是对新对象的引用)
  • #10 表示该main方法已完成并返回到调用它的任何内容。

所以new 字节码只确保对象的内存被创建,由它之后的字节码实际调用构造函数。

请注意,这可能意味着理论上您可以创建未初始化的对象并传递它们,但是 Java 运行时在它加载的字节码上运行一个称为“验证”的步骤,以验证这样的事情永远不会发生(即你可以'不仅仅是调用new并返回值,运行时将拒绝加载试图这样做的类)。

另请注意,#9 中的步骤基本上没有意义,因为我们写入了一个从不读取的局部变量。这表明它javac不是一个优化编译器:它非常直接地翻译 Java 源代码并且不尝试对其进行任何优化。诸如删除该存储操作之类的优化通常发生在运行时。

如果我们查看NewObject方法的字节码,我们会看到这个(删除了一些部分):

  public NewObject(int, int);
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1
         6: putfield      #2                  // Field varA:I
         9: aload_0
        10: iload_2
        11: putfield      #3                  // Field varB:I
        14: return

请注意,这args_size=3告诉我们该方法需要堆栈上的 3 个值(this和 2 个实参)。这意味着在此级别上,this引用被视为与任何其他参数一样。

  • 在第 0 行和第 1 行中,我们加载this堆栈并调用超级构造函数(`Object)
  • 第 4 行和第 5 行加载this,第一个参数a和第 6 行将设置字段 #2(这是varA对对象引用的this引用a
  • 第 9-11 行对 b 做同样的事情
  • 第 14 行标志着构造函数的结束。

推荐阅读