Почему статическая переменная, инициализируемая вызовом метода, возвращающая другую статическую переменную, остается пустой?

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

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
    }
}