Это порядок выполнения кода.Более подробная информация приведена ниже.
main()
- вызывает
Derived.<init>()
(неявный нулевой конструктор) - вызывает
Base.<init>()
- sets *От 1013 * до
1
. - вызывает
Derived.foo()
- отпечатков
Derived.x
, для которых по умолчанию установлено значение 0
- устанавливает
Derived.x
в 2
.
- вызывает
Derived.foo()
. - печатает
Derived.x
, что сейчас 2
.
Чтобы полностью понять, что происходит,Есть несколько вещей, которые вам нужно знать.
Field Shadowing
Base
'x
и Derived
' x
- это абсолютно разные поля, которые имеют одинаковыеназвание.Derived.foo
печатает Derived.x
, а не Base.x
, поскольку последний «затенен» первым.
Неявные конструкторы
Поскольку Derived
не имеет явного конструктора, компилятор создаетнеявный конструктор с нулевым аргументом.В Java каждый конструктор должен вызывать один конструктор суперкласса (за исключением 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. И вызовы super
не проходят через vtables, поскольку ониизначально не полиморфный.)
Каждый объект содержит указатель на дескриптор класса, который содержит vtable.Этот указатель устанавливается, как только объект выделен (с NEW
) и до вызова любых конструкторов.Поэтому в Java конструкторам безопасно выполнять вызовы виртуальных методов, и они будут должным образом направлены на реализацию целевого виртуального метода.
Поэтому, когда конструктор Base
вызывает foo()
, онвызывает Derived.foo
, который печатает Derived.x
.Но Derived.x
еще не назначен, поэтому значение по умолчанию 0
читается и печатается.