Странное поведение с использованием скобок в Java - PullRequest
40 голосов
/ 18 ноября 2011

Когда я запускаю следующий код:

public class Test {

  Test(){
    System.out.println("1");
  }

  {
    System.out.println("2");
  }

  static {
    System.out.println("3");
  }

  public static void main(String args[]) {
    new Test();
  }
}

Я ожидаю получить вывод в следующем порядке:

1
2
3

, но то, что я получил, находится в обратном порядке:

3
2
1

Может кто-нибудь объяснить, почему он выводится в обратном порядке?

================

Кроме того, когда я создаюболее одного экземпляра Test:

new Test();
new Test();
new Test();
new Test();

статический блок выполняется только в первый раз.

Ответы [ 9 ]

62 голосов
/ 18 ноября 2011

Все зависит от порядка выполнения операторов инициализации. Ваш тест показывает, что этот заказ:

  1. Статические блоки инициализации
  2. Блоки инициализации экземпляра
  3. Конструкторы

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

Спасибо за комментарии, теперь я могу процитировать соответствующую часть в спецификации JVM. Здесь это подробная процедура инициализации.

31 голосов
/ 18 ноября 2011

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

2 - это блок инициализатора, java-компилятор фактически скопирует его в каждый конструктор, так что вы можете разделить инициализацию между конструкторами, если хотите. Редко используется.

1 - будет выполнено при построении объекта, после (3) и (2) ..

Больше информации здесь

18 голосов
/ 18 ноября 2011

Статические блоки выполняются первыми.

А затем блоки инициализации экземпляра экземпляра

Пожалуйста, смотрите JLS для инициализаторов экземпляра

{

// оператор sop

}

вы не можете иметь возвращаемый статус в блоке инициализации экземпляра, как и конструкторы.

5 голосов
/ 18 ноября 2011

Сначала класс загружается в JVM и происходит инициализация класса. На этом этапе выполняются статические блоки. «{...}» является просто синтаксическим эквивалентом «static {...}». Поскольку в коде уже есть блок "static {...}", к нему будет добавлен "{...}". Вот почему у вас есть 3 напечатанных до 2.

Затем, когда класс загружен, java.exe (который, как я предполагал, вы выполнили из командной строки) найдет и запустит метод main. Основной статический метод инициализирует экземпляр, конструктор которого вызывается, поэтому вы печатаете «1» последним.

5 голосов
/ 18 ноября 2011
Test(){System.out.println("1");}

    {System.out.println("2");}

    static{System.out.println("3");}

статические объекты выполняются первыми, {System.out.println("2");} не является частью функции, поскольку из-за своей области она вызывается первой, а Test(){System.out.println("1");} вызывается последней, потому что две другие называются первыми

4 голосов
/ 22 ноября 2011

Не похоже, чтобы кто-то утверждал, что 3 явно напечатан только один раз. Поэтому я бы добавил, что это связано с тем, почему он печатается первым.

Статически определенный код помечается как отдельный от любого конкретного экземпляра класса. В целом, статически определенный код можно считать вовсе не классом (конечно, в этом утверждении есть некоторая недействительность при рассмотрении области видимости). Таким образом, этот код запускается после загрузки класса, как указано выше, например, не вызывается при создании экземпляра Test(), поэтому многократный вызов конструктора не приведет к статический код выполняется больше.

Код в скобках, содержащий 2 , добавляется перед конструкцией, как это было начато выше, поскольку он является своего рода предварительным условием для всех конструкторов в классе. Вы не знаете, что произойдет в конструкторах для Test, но вы гарантированно начнете с печати 2 . Таким образом, это происходит прежде всего в любом конкретном конструкторе и вызывается каждый раз, когда вызывается (ny) конструктор.

4 голосов
/ 21 ноября 2011

Полное объяснение

Порядок исполнения, как,

  1. статический блок
  2. экземпляр блока
  3. Конструктор

Объяснение

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

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

Блок инициализации экземпляра на самом деле вызывается перед конструктором?

После компиляции код станет,

public class Test {

  Test(){
    super();
    System.out.println("2");
    System.out.println("1");
  }


  static {
    System.out.println("3");
  }

  public static void main(String args[]) {
    new Test();
  }
}

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

Из этой документации

Компилятор Java копирует блоки инициализатора в каждый конструктор. Следовательно, этот подход можно использовать для разделения блока кода между несколькими конструкторами.

4 голосов
/ 18 ноября 2011

Я получил код, похожий на байт-код, здесь ASM.

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

public class Test {
static <clinit>() : void
GETSTATIC System.out : PrintStream
LDC "3"
INVOKEVIRTUAL PrintStream.println(String) : void
RETURN


<init>() : void
ALOAD 0: this
INVOKESPECIAL Object.<init>() : void
GETSTATIC System.out : PrintStream
LDC "2"
INVOKEVIRTUAL PrintStream.println(String) : void
GETSTATIC System.out : PrintStream
LDC "1"
INVOKEVIRTUAL PrintStream.println(String) : void
RETURN

public static main(String[]) : void
NEW Test
INVOKESPECIAL Test.<init>() : void
RETURN
}

мы можем видеть LDC "3" находится в "Clinit", это инициализатор класса.

Время жизни объекта обычно таково: класс загрузки -> класс связывания -> инициализация класса -> создание экземпляра объекта -> использование -> GC.Вот почему 3 появляется первым.И поскольку это на уровне класса, а не на уровне объекта, он появится один раз, поскольку тип класса будет загружен один раз.Для получения подробной информации, ссылка на внутри виртуальной машины Java2: время жизни типа

LDC "2" и `LDC "1" в конструкторе "init".

Причина в следующем порядке: конструктор сначала выполнит некоторую инструкцию по импликации, такую ​​как супер-конструктор и код в {} класса, а затем выполнит код, который явно указан в их конструкторе.

Это то, что будет делать компиляторв файл Java.

4 голосов
/ 18 ноября 2011

Поскольку код static{} запускается, когда класс впервые инициализируется в JVM (т. Е. Даже до вызова main()), экземпляр {} вызывается при первой инициализации экземпляра до его создания, и тогда конструктор вызывается после всего, что сделано.

...