Как @Mureinik сказал: «Одним словом, этот код совершенно легален». Я хочу дать более подробный ответ, потому что вы можете легко создавать проблемы при использовании статических инициализаторов в сочетании с методами класса - порядок появления может повлиять на состояние класса, и это неприятная ошибка для отслеживания.
статический инициализатор объявлены в классе выполняется при инициализации класса. Вместе с любыми полевыми инициализаторами для переменного класса ... статические инициализаторы могут быть использованы для инициализации класса переменного класса - Java Language Specification (JLS) §8.7
Инициализация обычно происходит в порядке появления (так называемый текстуального заказ). Например, рассмотрим следующий код:
class Bar {
static int i = 1;
static {i += 1;}
static int j = i;
}
class Foo {
static int i = 1;
static int j = i;
static {i += 1;}
public static void main(String[] args) {
System.out.println("Foo.j = " + Foo.j);
System.out.println("Bar.j = " + Bar.j);
}
}
Значение Foo.j
и Bar.j
отличаются из-за различий в текстологии упорядочения кода:
Foo.j = 1
Bar.j = 2
пример Ор в выполняется в текстовом порядке. Но что, если код был перестроен, скажем, в обратном порядке:
class Foo {
static { bar = generateValue(); } //originally 3rd
private static int generateValue() { return 123; } //originally 2nd
private final static int bar; //originally 1st
public static void main(String[] args) {
System.out.println("Foo.bar = " + Foo.bar);
}
}
Оказалось, что это компилируется без ошибок.Кроме того, выход: Foo.bar = 123
. Таким образом, bar
действительно содержит 123
во время выполнения. Однако, следующий код (от JLS §8.3.1.1) производит компиляцию ошибки времени, потому что он пытается получить доступ к j
до того j
объявлено:
//Don't do this!
class Z {
static { i = j + 2; } //Produces a compilation error
static int i, j;
static { j = 4; }
}
Интересно, что доступ методов не проверяется таким образом, так:
class Foo {
static int peek() { return j; }
static int i = peek();
static int j = 1;
public static void main(String[] args) {
System.out.println("Foo.i = " + Foo.i);
}
}
производит следующий вывод:
Foo.i = 0
это происходит потому, что
переменный инициализатор i
использует метод класса peek
для доступа к значению переменной j
перед j
инициализирован ее инициализатором переменным, в этот момент он по-прежнему имеет значение по умолчанию - JLS §8.3.2.3
Если вместо этого, i
инициализируется послеj
, то выход
Foo.i = 1
И ситуация становится хуже при использовании объектов вместо примитивных типов, как:
class Foo { //Don't do this
static int peek() { return j.hashCode(); } // NullPointerException here
static int i = peek();
static Object j = new Object();
public static void main(String[] args) {
System.out.println("Foo.i = " + Foo.i);
}
}
Это peek
бросает NullPointerException
при инициализации i
:
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException
at TestGame.Foo.peek(Foo.java:4)
at TestGame.Foo.<clinit>(Foo.java:5)
Eclipse, всплывает окно, когда выше код выполняется:
Привязав это обратно к OP, если вместо возврата 123
метод generateValue()
вернул значение некоторого другого статического поля (или метода), то значение bar
зависит от текстового упорядочения кода.
Итак, когда текстовое упорядочение важно?
Текстурный заказ не всегда используется. Иногда JVM ожидает выполнения инициализации. Важно знать, когда возможен внешний вид, и когда инициализация происходит в текстовом порядке. JLS описывает Restrictions on the use of Fields during Initialization in §8.3.2.3 (курсив мой собственный):
Заявление члена должно появиться текстуально, прежде чем она используется только если член [а] ... статическое поле класса или интерфейса C и все из следующих условий:
- использования происходит в [а] ... статический переменный инициализатор с или в [а] ... статический инициализатор С.
- использование не в левой части задания.
- Использование осуществляется по простому имени.
- C является самым внутренним классом или интерфейсом, охватывающим использование.
последнего замечание: константы инициализируются первым (на JLS §8.3.2.1):
Во время выполнения статических полей, которые являются окончательными и инициализируются с постоянными выражениями (§15.28) сначала инициализируются (§12.4.2).
** Да **. Я не знаю, что это ** гарантировано ** для работы, ваш код должен быть дефектным; и JVM должен быть неисправен (и ваша операционная система и компьютер). Было бы также ** законным ** писать 'private final static int bar = generateValue();' –