Вопрос-ответ

In what order do static/instance initializer blocks in Java run?

В каком порядке выполняются блоки статического инициализатора / экземпляра в Java?

Допустим, проект содержит несколько классов, каждый из которых имеет блок статического инициализатора. В каком порядке выполняются эти блоки? Я знаю, что внутри класса такие блоки выполняются в том порядке, в каком они появляются в коде. Я читал, что это одинаково для разных классов, но некоторые примеры кода, которые я написал, с этим не согласны. Я использовал этот код:

package pkg;

public class LoadTest {
public static void main(String[] args) {
System.out.println("START");
new Child();
System.out.println("END");
}
}

class Parent extends Grandparent {
// Instance init block
{
System.out.println("instance - parent");
}

// Constructor
public Parent() {
System.out.println("constructor - parent");
}

// Static init block
static {
System.out.println("static - parent");
}
}

class Grandparent {
// Static init block
static {
System.out.println("static - grandparent");
}

// Instance init block
{
System.out.println("instance - grandparent");
}

// Constructor
public Grandparent() {
System.out.println("constructor - grandparent");
}
}

class Child extends Parent {
// Constructor
public Child() {
System.out.println("constructor - child");
}

// Static init block
static {
System.out.println("static - child");
}

// Instance init block
{
System.out.println("instance - child");
}
}

и получили этот результат:


START
статический - прародительский
статический - родительский
статический - дочерний
экземпляр - прародительский
конструктор - прародительский
экземпляр - родительский
конструктор - родительский
экземпляр - дочерний
конструктор - дочерний
END


Очевидный ответ из этого заключается в том, что родительские блоки выполняются раньше дочерних, но это может быть просто совпадением и не помогает, если два класса не находятся в одной иерархии.

Редактировать:

Я изменил свой пример кода, добавив это к LoadTest.java:

class IAmAClassThatIsNeverUsed {
// Constructor
public IAmAClassThatIsNeverUsed() {
System.out.println("constructor - IAACTINU");
}

// Instance init block
{
System.out.println("instance - IAACTINU");
}

// Static init block
static {
System.out.println("static - IAACTINU");
}
}

Как следует из названия класса, я нигде не ссылался на новый класс. Новая программа выдала тот же результат, что и старая.

Переведено автоматически
Ответ 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 также вызывает инициализацию класса или интерфейса. Класс или интерфейс не будут инициализированы ни при каких других обстоятельствах.


Чтобы проиллюстрировать, вот пошаговое руководство того, что происходит в примере:


  1. Введите main

  2. Вывести "НАЧАТЬ"

  3. Попытка создать первый экземпляр дочернего элемента, для чего требуется инициализация дочернего элемента

  4. Попытка инициализировать дочерний блок вызывает инициализацию родительского

  5. Попытка инициализировать родительский элемент вызывает инициализацию дедушкиного элемента

  6. В начале инициализации Grandparent запускается блок статической инициализации Grandparent

  7. Технически, Object получает последнее слово в цепочке инициализации в силу того, что является родительским объектом Grandparent , но ему нечего добавить

  8. После завершения блока статической инициализации прародителя программа возвращается к родительскому блоку статической инициализации

  9. После завершения родительского блока статической инициализации программа возвращается к дочернему блоку статической инициализации

  10. На этом этапе дочерний элемент инициализируется, поэтому его конструктор может продолжить работу

  11. Поскольку на iamaclasssthatisneverused никогда не ссылаются, ни один из его кодов никогда не выполняется, включая блоки статического инициализатора

  12. Остальная часть этого пошагового руководства не касается статических инициализаторов и включена только для полноты картины

  13. Дочерний конструктор неявно вызывает super() (т.Е. Родительский конструктор)

  14. Родительский конструктор неявно вызывает super() (т. Е. Конструктор дедушки)

  15. Конструктор дедушки и бабушки делает то же самое, что не имеет никакого эффекта (опять же, объекту нечего вносить)

  16. Сразу после вызова конструктора Grandparent в super () приходит блок инициализатора экземпляра Grandparent

  17. Выполняется остальная часть конструктора дедушки и бабушки, и конструктор завершается

  18. Программа возвращается к родительскому конструктору сразу после того, как ее вызов super() (т. Е. Конструктора Grandparent) разрешает

  19. Как указано выше, инициализатор родительского экземпляра выполняет свое дело, и его конструктор завершает работу

  20. Аналогично, программа возвращается к дочернему конструктору и завершает его

  21. На этом этапе экземпляр объекта создан

  22. Вывести "END"

  23. Завершается нормально

Ответ 4

Есть один случай, в котором статический блок вызываться не будет.

class Super {
public static int i=10;
}
class Sub extends Super {
static {
system.out.println("Static block called");
}
}
class Test {
public static void main (String [] args) {
system.out.println(Sub.i);
}
}

Приведенный выше код выводит 10


Обновление из "редактора"

Техническое объяснение этого находится в 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 объясняет, что это разрешено, и позволяет вам точно определить, что произойдет на практике.)

2024-03-04 17:12 java