这是代码的执行顺序。更多细节如下。
- 
main()
- invokes(隐式空构造函数)Derived.<init>()
- 调用Base.<init>()
- 设置为 。Base.x1
- 调用Derived.foo()
 
- 设置为 。Derived.x2
 
- 调用。Derived.foo()
 
要完全了解正在发生的事情,您需要了解几件事。
场阴影
Base的 和 是完全不同的字段,碰巧具有相同的名称。 打印,不是,因为后者被前者“阴影”。xDerivedxDerived.fooDerived.xBase.x
隐式构造函数
由于没有显式构造函数,编译器将生成一个隐式零参数构造函数。在 Java 中,每个构造函数都必须调用一个超类构造函数(没有超类的除外),这使超类有机会安全地初始化其字段。编译器生成的空构造函数仅调用其超类的空构造函数。(如果超类没有空构造函数,则会产生编译错误。DerivedObject
因此,的隐式构造函数看起来像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
}
现在让我们回到 和 。如果我们反编译它们的构造函数,我们会看到类似的东西BaseDerived
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
所以当 的构造函数调用 时,它会调用 ,它打印 。但尚未分配,因此 默认值为读取和打印。Basefoo()Derived.fooDerived.xDerived.x0