Класс C
переопределяет A.foo()
, и полиморфизм активен даже в конструкторе в Java.Поэтому, когда конструктор в A
вызывает foo()
, когда мы создаем экземпляр C
, на самом деле вызывается C.foo()
.
C.foo()
в свою очередь выводит B.i
,таким образом, мы можем ожидать, что 6
будет распечатан - но инициализаторы переменных экземпляра выполняются только после конструкторов суперкласса, поэтому в точке выполнения B.i
равно 0.
По сути, порядок выполнения конструктора:
- Выполнение связанных конструкторов, либо явно
this(...)
для цепочки в другой конструктор в том же классе, либо явно super(...)
для цепочкив конструктор в суперклассе или неявно super()
для цепочки в конструктор без параметров в суперклассе. - Для конструкторов, которые связаны в цепочку с конструктором суперкласса, выполните инициализаторы переменных.
- Executeкод в теле конструктора
Перезапись кода во избежание использования инициализаторов переменных и теневого копирования делает это более понятным,при сохранении эквивалентного кода:
public class A {
int ai;
public A() {
super();
ai = 5;
foo();
}
public void foo() {
System.out.println(ai);
}
}
class B extends A {
int bi;
public B() {
super();
bi = 6;
}
}
class C extends B {
int ci;
public C() {
super();
ci = 7;
}
public void foo() {
System.out.print(bi);
}
public static void main(String[] args) {
C c = new C();
}
}
Кстати, «скрывающая переменную» часть этого не вступит в игру, если вы сделаете все свои поля приватными, с чего я и начинаю.буду рекомендоватьЭто просто оставляет проблемы вызова виртуальных методов из конструктора, что, как правило, является плохой идеей из-за ожидания того, что объект сможет работать до того, как он получит полную инициализацию, и, возможно, неожиданного времени выполнения инициализатора переменной.
Если вы избегаете вызова виртуальных методов из конструкторов, даже время инициализации переменных становится неактуальным - по крайней мере, почти всегда.