这是对 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);
}
}
后台(我把最让我疑惑的地方用斜体标出):
- 声明了 class
NewObject
的新引用变量 obj
;
- 运算符
new
请求 Java 一些堆内存以使用 class NewObject
的声明作为蓝图来分配对象。
- 运算符
new
将Java提供的内存块地址存储在变量obj
里面
- 运算符
new
然后 在新创建的对象中调用构造函数 并传递两个数值(明确地为 3 和 4), 以及作为此对象的地址(this
隐式) 作为参数。
- 在对象 中,构造函数创建了两个局部变量
a
和 b
并将从 new
接收到的值赋给它们。 构造函数还隐含地创建了一个本地this
来存储对象的地址。
- 构造函数在其主体中看到
varA
和 varB
,但看不到附加到它们的显式 this
,因此它首先将它们视为局部变量。由于找不到这些局部变量对应的声明,于是认为它们一定是实例变量。
- 构造函数因此搜索隐式
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 行标记构造函数的结尾。
我是 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);
}
}
后台(我把最让我疑惑的地方用斜体标出):
- 声明了 class
NewObject
的新引用变量obj
; - 运算符
new
请求 Java 一些堆内存以使用 classNewObject
的声明作为蓝图来分配对象。 - 运算符
new
将Java提供的内存块地址存储在变量obj
里面
- 运算符
new
然后 在新创建的对象中调用构造函数 并传递两个数值(明确地为 3 和 4), 以及作为此对象的地址(this
隐式) 作为参数。 - 在对象 中,构造函数创建了两个局部变量
a
和b
并将从new
接收到的值赋给它们。 构造函数还隐含地创建了一个本地this
来存储对象的地址。 - 构造函数在其主体中看到
varA
和varB
,但看不到附加到它们的显式this
,因此它首先将它们视为局部变量。由于找不到这些局部变量对应的声明,于是认为它们一定是实例变量。 - 构造函数因此搜索隐式
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 argumenta
and line #6 will set the field #2 (which is a reference tovarA
of the objectthis
引用a
) 的值
- 第 9-11 行对 b 执行相同的操作
- 第 14 行标记构造函数的结尾。