这是对 Java 中创建新对象过程的正确描述吗?

Is that a correct depiction of the process of creating a new object in Java?

我是 Java 和 OOP 的新手。在多次尝试弄清楚创建新对象的过程后,我仍然怀疑我是否正确理解了内部到底发生了什么(比如 'What is the role of operator new?'、'Who calls the constructor?'、'How does the constructor know what object to initialize?' 'Is this present at all stages or not?',等等)。

假设我们有一个代码:

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

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

让我们看一下您的主要方法的反编译字节码(我遗漏了一些不太相关的部分):

  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 引用的类型的新对象(它旁边的注释很好地告诉我们是 class NewObject
  • #3 表示 "duplicate the latest value on the stack"(恰好是对新分配对象的引用)。现在堆栈包含 2 个对新对象的引用。
  • #4 和#5 将数字 3 和 4 入栈
  • #6 使用 invokespecial 通过引用 #3 调用构造函数。这将从堆栈中获取所需的参数,并将它们从堆栈中弹出(堆栈中的最后 3 个值是对新对象和数字 3 和 4 的引用)
  • #9 会将堆栈中的剩余值存储在局部变量 #1(这是对新对象的引用)
  • #10 表示 main 方法已完成,return 到调用它的任何地方。

所以new字节码只确保为对象创建内存,实际调用构造函数取决于它后面的字节码。

请注意,此 可能 意味着理论上您可以创建未初始化的对象并将它们传递,但 Java 运行时运行一个名为 "verification" 在它加载的字节码上验证这样的事情永远不会发生(即你不能只调用 new 和 return 值,运行时将拒绝加载 class 试图做到这一点)。

另请注意,#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)的超级构造函数
  • lines #4 and #5 load this and the first argument a and line #6 will set the field #2 (which is a reference to varA of the object this 引用 a)
  • 的值
  • 第 9-11 行对 b
  • 执行相同的操作
  • 第 14 行标记构造函数的结尾。