这是代码的执行顺序。更多细节如下。
-
main()
- invokes(隐式空构造函数)
Derived.<init>()
- 调用
Base.<init>()
- 设置为 。
Base.x
1
- 调用
Derived.foo()
- 设置为 。
Derived.x
2
- 调用。
Derived.foo()
要完全了解正在发生的事情,您需要了解几件事。
场阴影
Base
的 和 是完全不同的字段,碰巧具有相同的名称。 打印,不是,因为后者被前者“阴影”。x
Derived
x
Derived.foo
Derived.x
Base.x
隐式构造函数
由于没有显式构造函数,编译器将生成一个隐式零参数构造函数。在 Java 中,每个构造函数都必须调用一个超类构造函数(没有超类的除外),这使超类有机会安全地初始化其字段。编译器生成的空构造函数仅调用其超类的空构造函数。(如果超类没有空构造函数,则会产生编译错误。Derived
Object
因此,的隐式构造函数看起来像Derived
public Derived() {
super();
}
初始值设定项块和字段定义
初始值设定项块按声明顺序组合,形成一个大代码块,该代码块插入到所有构造函数中。具体来说,它是在调用之后插入的,但在构造函数的其余部分之前插入的。字段定义中的初始值分配被视为初始值设定项块。super()
因此,如果我们有
class Test {
{x=1;}
int x = 2;
{x=3;}
Test() {
x = 0;
}
}
这等效于
class Test {
int x;
{
x = 1;
x = 2;
x = 3;
}
Test() {
x = 0;
}
}
这就是编译的构造函数的实际外观:
Test() {
// implicit call to the superclass constructor, Object.<init>()
super();
// initializer blocks, in declaration order
x = 1
x = 2
x = 3
// the explicit constructor code
x = 0
}
现在让我们回到 和 。如果我们反编译它们的构造函数,我们会看到类似的东西Base
Derived
public Base() {
super(); // Object.<init>()
x = 1; // assigns Base.x
foo();
}
public Derived() {
super(); // Base.<init>()
x = 2; // assigns Derived.x
}
虚拟调用
在 Java 中,实例方法的调用通常通过虚拟方法表。(也有例外。构造函数、私有方法、最终方法和最终类的方法不能被重写,因此无需通过 vtable 即可调用这些方法。并且调用不会通过 vtables,因为它们本质上不是多态的。super
每个对象都有一个指向类句柄的指针,该类句柄包含一个 vtable。一旦分配了对象(with ),就会在调用任何构造函数之前设置此指针。因此,在Java中,构造函数进行虚拟方法调用是安全的,并且它们将被正确定向到虚拟方法的目标实现。NEW
所以当 的构造函数调用 时,它会调用 ,它打印 。但尚未分配,因此 默认值为读取和打印。Base
foo()
Derived.foo
Derived.x
Derived.x
0