In what order do static/instance initializer blocks in Java run?
В каком порядке выполняются блоки статического инициализатора / экземпляра в Java?
Допустим, проект содержит несколько классов, каждый из которых имеет блок статического инициализатора. В каком порядке выполняются эти блоки? Я знаю, что внутри класса такие блоки выполняются в том порядке, в каком они появляются в коде. Я читал, что это одинаково для разных классов, но некоторые примеры кода, которые я написал, с этим не согласны. Я использовал этот код:
Очевидный ответ из этого заключается в том, что родительские блоки выполняются раньше дочерних, но это может быть просто совпадением и не помогает, если два класса не находятся в одной иерархии.
Редактировать:
Я изменил свой пример кода, добавив это к LoadTest.java:
Как следует из названия класса, я нигде не ссылался на новый класс. Новая программа выдала тот же результат, что и старая.
Переведено автоматически
Ответ 1
Смотрите разделы 12.4 и 12.5 JLS версии 8, там подробно рассказывается обо всем этом (12.4 для статических и 12.5 для переменных экземпляра).
Для статической инициализации (раздел 12.4):
Класс или интерфейс типа T будет инициализирован непосредственно перед первым появлением любого из следующих:
T - это класс, и создается экземпляр T.
T - это класс, и вызывается статический метод, объявленный T .
Назначается статическое поле, объявленное с помощью T .
Используется статическое поле, объявленное T, и это поле не является постоянной переменной (§ 4.12.4).
T - это класс верхнего уровня (§7.6), и выполняется оператор assert (§ 14.10), лексически вложенный в T (§8.1.3).
(и несколько предложений типа weasel-word)
Ответ 2
Статический инициализатор для класса запускается при первом обращении к классу либо для создания экземпляра, либо для доступа к статическому методу или полю.
Итак, для нескольких классов это полностью зависит от кода, который выполняется, чтобы вызвать загрузку этих классов.
Ответ 3
Ответы Кита и Криса оба замечательные, я просто добавляю еще немного деталей для моего конкретного вопроса.
Статические блоки инициализации выполняются в том порядке, в котором инициализируются их классы. Итак, в каком это порядке? Согласно JLS 12.4.1:
Класс или интерфейс типа T будет инициализирован непосредственно перед первым появлением любого из следующих:
T - это класс, и создается экземпляр T.
T - это класс, и вызывается статический метод, объявленный T .
Назначается статическое поле, объявленное с помощью T .
Используется статическое поле, объявленное T, и это поле не является постоянной переменной (§ 4.12.4).
T - это класс верхнего уровня, и выполняется оператор assert (§ 14.10), лексически вложенный в T.
Вызов определенных отражающих методов в классе Class и в пакете java.lang.reflect также вызывает инициализацию класса или интерфейса. Класс или интерфейс не будут инициализированы ни при каких других обстоятельствах.
Чтобы проиллюстрировать, вот пошаговое руководство того, что происходит в примере:
Введите main
Вывести "НАЧАТЬ"
Попытка создать первый экземпляр дочернего элемента, для чего требуется инициализация дочернего элемента
Попытка инициализировать дочерний блок вызывает инициализацию родительского
Попытка инициализировать родительский элемент вызывает инициализацию дедушкиного элемента
В начале инициализации Grandparent запускается блок статической инициализации Grandparent
Технически, Object получает последнее слово в цепочке инициализации в силу того, что является родительским объектом Grandparent , но ему нечего добавить
После завершения блока статической инициализации прародителя программа возвращается к родительскому блоку статической инициализации
После завершения родительского блока статической инициализации программа возвращается к дочернему блоку статической инициализации
На этом этапе дочерний элемент инициализируется, поэтому его конструктор может продолжить работу
Поскольку на iamaclasssthatisneverused никогда не ссылаются, ни один из его кодов никогда не выполняется, включая блоки статического инициализатора
Остальная часть этого пошагового руководства не касается статических инициализаторов и включена только для полноты картины
Дочерний конструктор неявно вызывает super() (т.Е. Родительский конструктор)
Родительский конструктор неявно вызывает super() (т. Е. Конструктор дедушки)
Конструктор дедушки и бабушки делает то же самое, что не имеет никакого эффекта (опять же, объекту нечего вносить)
Сразу после вызова конструктора Grandparent в super () приходит блок инициализатора экземпляра Grandparent
Выполняется остальная часть конструктора дедушки и бабушки, и конструктор завершается
Программа возвращается к родительскому конструктору сразу после того, как ее вызов super() (т. Е. Конструктора Grandparent) разрешает
Как указано выше, инициализатор родительского экземпляра выполняет свое дело, и его конструктор завершает работу
Аналогично, программа возвращается к дочернему конструктору и завершает его
На этом этапе экземпляр объекта создан
Вывести "END"
Завершается нормально
Ответ 4
Есть один случай, в котором статический блок вызываться не будет.
Техническое объяснение этого находится в JLS 12.4.1
"Ссылка на статическое поле (§8.3.1.1) вызывает инициализацию только класса или интерфейса, который фактически его объявляет, даже если на него можно ссылаться через имя подкласса, подинтерфейса или класса, реализующего интерфейс".
Интуитивно понятное объяснение заключается в том, что Super.i и Sub.i на самом деле являются одной и той же переменной, и ничего в Sub на самом деле не нужно инициализировать, чтобы Super.i получить правильное значение.
(Было бы иначе, если бы выражение инициализации для Super.i ссылалось на Sub класс. Но тогда у вас был бы цикл в порядке инициализации. Внимательное прочтение JLS 12.4.1 и JLS 12.4.2 объясняет, что это разрешено, и позволяет вам точно определить, что произойдет на практике.)