“验证器拒绝在初始化之前使用新对象的代码。”
在字节码验证中,由于验证器是在链接时工作的,因此可以推断方法的局部变量的类型。方法参数的类型在类文件的方法签名中是已知的。其他局部变量的类型是未知的并且是推断出来的,所以我假设上面陈述中的“用途”与此有关。
编辑: JVMS的第 4.9.4 节内容如下:
myClass 类的实例初始化方法(第 3.9 节)将新的未初始化对象视为其在局部变量 0 中的 this 参数。在该方法调用 myClass 或其直接超类的另一个实例初始化方法之前,该方法可以执行的唯一操作这是分配在 myClass 中声明的字段。
上述语句中的字段分配是在为对象分配内存时将实例变量“初始”初始化为默认初始值(如 int 为 0,float 为 0.0f 等)。当虚拟机在对象上调用实例初始化方法(构造函数)时,还有一个实例变量的“正确”初始化。John Horstmann 提供
的链接有助于澄清事情。所以这些说法不成立。“这并不意味着在一个<init>
方法中,getfield
并且putfield
在另一个方法被调用之前被允许<init>
。” getfield
和_putfield
指令用于访问(和更改)类(或类的实例)的实例变量(字段)。只有在初始化实例变量(字段)时才会发生这种情况。”
从JVMS:
每个实例初始化方法(第 3.9 节),除了从类 Object 的构造函数派生的实例初始化方法外,必须在访问其实例成员之前调用 this 的另一个实例初始化方法或其直接超类 super 的实例初始化方法。但是,在当前类中声明的 this 的实例字段可以在调用任何实例初始化方法之前分配。
当 Java 虚拟机隐式或显式地创建一个类的新实例时,它首先在堆上分配内存来保存对象的实例变量。为对象类及其所有超类中声明的所有变量分配内存,包括隐藏的实例变量。一旦虚拟机为新对象预留了堆内存,它就会立即将实例变量初始化为默认初始值。一旦虚拟机为新对象分配了内存并将实例变量初始化为默认值,它就可以为实例变量提供适当的初始值。Java 虚拟机使用两种技术来执行此操作,具体取决于对象是否由于 clone() 调用而被创建。如果由于 clone() 正在创建对象,则虚拟机将被克隆对象的实例变量的值复制到新对象中。否则,虚拟机调用对象的实例初始化方法。实例初始化方法将对象的实例变量初始化为其正确的初始值。只有在此之后,您才能使用getfield
and putfield
。
java 编译器为其编译的每个类生成至少一个实例初始化方法(构造函数)。如果类没有显式声明构造函数,编译器会生成一个默认的无参数构造函数,它只调用超类的无参数构造函数。在调用super()
或this()
导致编译错误之前对实例字段进行任何操作是正确的。
一个<init>
方法可以包含三种代码:另一个<init>
方法的调用,实现任何实例变量初始化器的代码,以及构造函数主体的代码。如果构造函数以显式调用同一类中的另一个构造函数开始(this()
调用),则其对应的<init>
方法将由两部分组成:
- 同类
<init>
方法的调用
- 实现相应构造函数主体的字节码
如果构造函数不是以调用开头this()
且类不是 Object,则该<init>
方法将具有三个组件:
- 调用超类
<init>
方法
- 任何实例变量初始化器的字节码
- 实现相应构造函数主体的字节码
如果构造函数不以this()
调用开头并且类是 Object(并且 Object 没有超类),则其方法不能以超类方法调用<init>
开头。<init>
如果构造函数以显式调用超类构造函数开始(super()
调用),则其<init>
方法将调用相应的超类<init>
方法。
我认为这回答了你的第一个和第二个问题。
更新:
例如,
class Demo
{
int somint;
Demo() //first constructor
{
this(5);
//some other stuff..
}
Demo(int i) //second constructor
{
this.somint = i;
//some other stuff......
}
Demo(int i, int j) //third constructor
{
super();
//other stuffff......
}
}
下面是来自编译器(javac)的上述三个构造函数的字节码:
Demo();
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: iconst_5
2: invokespecial #1; //Method "<init>":(I)V
5: return
Demo(int);
Code:
Stack=2, Locals=2, Args_size=2
0: aload_0
1: invokespecial #2; //Method java/lang/Object."<init>":()V
4: aload_0
5: iload_1
6: putfield #3; //Field somint:I
9: return
Demo(int, int);
Code:
Stack=1, Locals=3, Args_size=3
0: aload_0
1: invokespecial #2; //Method java/lang/Object."<init>":()V
4: return
在第一个构造函数中,<init>
方法从调用同类方法开始<init>
,然后执行相应构造函数的主体。因为构造函数以 a 开头,所以this()
其对应的<init>
方法不包含用于初始化实例变量的字节码。
在第二个构造函数中,构造函数的<init>
方法有
- 超类
<init>
方法,即调用超类构造函数(无arg方法),编译器默认生成this,因为super()
第一条语句没有找到显式。
- 用于初始化实例变量的字节码
someint
。
- 构造函数主体中其余内容的字节码。