首先要了解的是对象实例化如何在字节码级别工作。
如JVMS,§3.8 中所述。使用类实例:
Java 虚拟机类实例是使用 Java 虚拟机的新指令创建的。回想一下,在 Java 虚拟机级别,构造函数显示为具有编译器提供的名称的方法<init>
。这种特殊命名的方法称为实例初始化方法(第 2.9 节)。对于给定的类,可能存在对应于多个构造函数的多个实例初始化方法。一旦创建了类实例并且其实例变量(包括类及其所有超类的变量)已初始化为其默认值,则调用新类实例的实例初始化方法。例如:
Object create() {
return new Object();
}
编译为:
Method java.lang.Object create()
0 new #1 // Class java.lang.Object
3 dup
4 invokespecial #4 // Method java.lang.Object.<init>()V
7 areturn
因此,构造函数调用 via共享作为第一个参数invokespecial
传递的行为。this
invokevirtual
但是,必须强调的是,对未初始化引用的引用是特殊处理的,因为在调用构造函数(或在构造函数内部时的超级构造函数)之前不允许使用它。这是由验证者强制执行的。
JVMS,§4.10.2.4。实例初始化方法和新创建的对象:
...类的实例初始化方法(第 2.9 节)myClass
将新的未初始化对象视为其this
在局部变量 0 中的参数。在该方法调用另一个实例初始化方法myClass
或其直接超类 on之前this
,该方法可以执行的唯一操作this
是分配字段内声明myClass
。
在对实例方法进行数据流分析时,验证器将局部变量 0 初始化为包含当前类的对象,或者,对于实例初始化方法,局部变量 0 包含表示未初始化对象的特殊类型。在此对象上(从当前类或其直接超类)调用适当的实例初始化方法后,此特殊类型在验证器的操作数堆栈模型和局部变量数组中的所有出现都将替换为当前类类型。验证器拒绝在新对象初始化之前使用它或多次初始化该对象的代码。此外,它确保方法的每个正常返回都调用了该方法的类或直接超类中的实例初始化方法。
类似地,作为 Java 虚拟机指令new的结果,创建一个特殊类型并将其推送到操作数堆栈的验证者模型上。特殊类型表示创建类实例的指令和创建的未初始化类实例的类型。当在该类实例上调用在未初始化的类实例的类中声明的实例初始化方法时,所有出现的特殊类型都将替换为该类实例的预期类型。随着数据流分析的进行,这种类型的变化可能会传播到后续指令。
因此,通过new指令创建对象的代码在调用构造函数之前不能以任何方式使用它,而构造函数的代码只能在调用另一个(this(…)
或super(…)
)构造函数之前分配字段(内部类用来初始化的机会他们的外部实例引用作为第一个动作),但仍然不能用他们未初始化的 this 做任何其他事情。
this
当仍处于未初始化状态时,也不允许构造函数返回。因此,自动生成的构造函数具有所需的最小值,调用超级构造函数并返回(在字节码级别没有隐式返回)。
通常建议您阅读Java® 虚拟机规范(即其Java 11 版本)以及任何 ASM 特定文档或教程。