Порядок инициализации конечных полей
Рассмотрим эти два класса:
public abstract class Bar {
protected Bar() {
System.out.println(getValue());
}
protected abstract int getValue();
}
public class Foo extends Bar {
private final int i = 20;
public Foo() {
}
@Override
protected int getValue() {
return i;
}
public static void main(String[] args) {
new Foo();
}
}
Если я выполнил Foo, выход будет равен 20.
Если я делаю поле не финальным, или если я инициализирую его в конструкторе Foo, вывод будет 0.
Мой вопрос: каков порядок инициализации в случае конечных полей и где это поведение описано в JLS?
Я ожидал найти какое-то исключительное правило о конечных полях здесь, но если я не пропущу что-то, не будет.
Обратите внимание, что я знаю, что я никогда не должен вызывать переопределяемый метод из конструктора. Это не вопрос вопроса.
Ответы
Ответ 1
Ваша переменная-член final int i
является постоянной переменной: 4.12.4. final
Переменные
Переменная примитивного типа или типа String
, то есть final
и инициализированная выражением константы времени компиляции (§15.28), называется постоянной переменной.
Это имеет последствия для порядка, в котором вещи инициализируются, как описано в 12.4.2. Подробная процедура инициализации.
Ответ 2
Прогулка по тому, как это выглядит в стиле "байт-код-иш".
Вы уже должны знать, что первая фактическая инструкция в конструкторе должна быть вызовом super
(с аргументами или без).
Эта супер команда возвращается, когда родительский конструктор завершен, и супер-объект полностью сконструирован. Поэтому, когда вы строите Foo
, следующее происходит (по порядку):
// constant fields are initialized by this point
Object.construction // constructor call of Object, done by Bar
Bar.construction // aka: Foo.super()
callinterface getValue() // from Bar constructor
// this call is delegated to Foo, since that the actual type responsible
// and i is returned to be printed
Foo.construction
если вы должны были инициализировать его в конструкторе, это произойдет "сейчас", после того, как getValue()
уже был вызван.
Ответ 3
Вот еще более подлая версия, просто для удовольствия.
public abstract class Bar {
protected Bar() {
System.out.println(getValue());
}
protected abstract Object getValue();
}
public class Foo extends Bar {
private final String i = "Hello";
public Foo() {
}
@Override
protected Object getValue() {
return i;
}
public static void main(String[] args) {
new Foo();
}
}
Результат: Печать Hello
.
Теперь измените эту строку:
private final String i = "Hello";
в
private final Object i = "Hello";
Результат: Печать null
.
Это связано с тем, что String
(и примитивные типы) обрабатываются специально, как описано в JLS 4.12.4, о котором я упоминал в своем другом ответе.