Почему статическая переменная, инициализируемая вызовом метода, возвращающая другую статическую переменную, остается пустой?
Я просто не понимаю следующий поток выполнения кода:
class Test {
static String s1 = getVal();
static String s2 = "S2";
private static String getVal() {
return s2;
}
public static void main(String args[]) {
System.out.println(s2); // prints S2
System.out.println(s1); // prints null
}
}
Предполагается напечатать S2
во втором выражении println
. Меня больше интересует, почему это происходит, а не решение.
Ответы
Ответ 1
Статические вещи выполняются в том порядке, в котором они отображаются в коде.
static String s1 = getVal();
Итак, начиная с первой строки, s1
получает оценку и к тому времени s2
все еще null
. Следовательно, вы видите нулевое значение.
Ответ 2
static
переменные и static
блоки инициализации инициализируются в порядке их появления в исходном коде (кроме переменных static final
, которые инициализируются перед переменными static
).
s1
инициализируется до s2
, поэтому вызов getVal()
возвращает значение по умолчанию s2
, которое равно null
.
Вы можете изменить порядок переменных static
, чтобы сначала инициализировать s2
:
static String s2 = "S2";
static String s1 = getVal();
Еще один способ заставить инициализацию s2
выполнить до s1
сделать s2
final:
static String s1 = getVal();
static final String s2 = "S2";
Ниже приведен фрагмент порядка инициализации, как указано в JLS 12.4.2. Подробная процедура инициализации. Выделены разделы, относящиеся к переменным static
.
Для каждого класса или интерфейса C существует уникальный замок инициализации LC. Отображение от C до LC остается на усмотрение реализации виртуальной машины Java. Процедура инициализации C следующая:
-
Синхронизировать блокировку инициализации, LC, для C. Это предполагает ожидание, пока текущий поток не сможет получить LC.
-
Если объект класса для C указывает, что инициализация выполняется для C другим потоком, затем отпустите LC и заблокируйте текущий поток, пока не сообщите, что завершена инициализация в процессе выполнения, и в этот момент повторите этот шаг.
-
Если объект класса для C указывает, что инициализация выполняется для C текущим потоком, тогда это должен быть рекурсивный запрос для инициализации. Отпустите LC и закончите нормально.
-
Если объект класса для C указывает, что C уже был инициализирован, никаких дополнительных действий не требуется. Отпустите LC и закончите нормально.
-
Если объект класса для C находится в ошибочном состоянии, тогда инициализация невозможна. Выпустите LC и выпустите NoClassDefFoundError.
-
В противном случае запишите тот факт, что инициализация объекта класса для C выполняется текущим потоком и освобождение LC.
Затем инициализировать статические поля C, которые являются постоянными переменными (§4.12.4, §8.3.2, §9.3.1).
-
Далее, если C - это класс, а не интерфейс, то пусть SC - его суперкласс и пусть SI1,..., SIn - все суперинтерфейсы C, объявляющие хотя бы один метод по умолчанию. Порядок суперинтерфейсов задается рекурсивным перечислением над иерархией суперинтерфейса каждого интерфейса, непосредственно реализованного C (в левом-правом порядке предложения C реализует). Для каждого интерфейса, который я непосредственно реализует с помощью C, перечисление повторяется на я суперинтерфейсах (в порядке слева направо предложения я extends) перед возвратом I.
Для каждого S в списке [SC, SI1,..., SIn], если S еще не инициализировано, тогда рекурсивно выполнить всю эту процедуру для S. Если необходимо, сначала проверьте и подготовьте S.
Если инициализация S завершается внезапно из-за возникшего исключения, затем приобретите LC, пометьте объект класса для C как ошибочный, уведомите все ожидающие потоки, отпустите LC и завершите внезапно, выбросив ту же исключение, которое было результатом инициализации S.
-
Затем определите, разрешены ли утверждения (§14.10) для C, запрашивая его определяющий загрузчик классов.
-
Далее выполнить либо инициализаторы переменной класса, либо статические инициализаторы класса, или инициализаторы полей интерфейса, в текстовом порядке, как если бы они были одним блоком.
Ответ 3
В соответствии с JLS раздел 12.4.2 статические поля инициализируются следующим образом:
Затем выполните либо инициализаторы переменной класса, либо статические инициализаторы класса, или инициализаторы полей интерфейса, в текстовом порядке, как если бы они были одиночными блок.
Итак, s1
инициализируется первым, в этот момент s2
не инициализируется, поэтому он имеет значение по умолчанию String
, которое равно null
.
И затем s2
получает инициализацию до "S2"
, но s1
остается null
.
Просто измените порядок двух объявлений, чтобы решить эту проблему.
Ответ 4
s1 сначала инициализируется, а значение s2 равно нулю в это время.
s1 пытается вернуть значение s2, которое было инициализировано позже.
Вот почему вы получаете его как null.
Если вы попытаетесь сделать это, вы получите ожидаемый ответ "S2"
class Test {
static String s2 = "S2";
static String s1 = getVal();
private static String getVal() {
return s2;
}
public static void main(String args[]) {
System.out.println(s2); // prints S2
System.out.println(s1); // prints S2
}
}