Подтвердить что ты не робот

Странное поведение Java со статическими и финальными квалификаторами

В нашей команде мы обнаружили какое-то странное поведение, в котором мы использовали квалификаторы static и final. Это наш тестовый класс:

public class Test {

    public static final Test me = new Test();
    public static final Integer I = 4;
    public static final String S = "abc";

    public Test() {
        System.out.println(I);
        System.out.println(S);
    }

    public static Test getInstance() { return me; }

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

Когда мы запускаем метод main, получаем результат:

null
abc

Я бы понял, написал ли он значения null оба раза, так как код статических членов класса выполняется сверху вниз.

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

4b9b3361

Ответ 1

Это шаги, предпринятые при запуске вашей программы:

  • До запуска main класс Test должен быть инициализирован путем запуска статических инициализаторов в порядке появления.
  • Чтобы инициализировать поле me, начните выполнение new Test().
  • Распечатайте значение I. Поскольку тип поля Integer, то, что похоже на константу времени компиляции 4, становится вычисленным значением (Integer.valueOf(4)). Инициализатор этого поля еще не запущен, напечатав начальное значение null.
  • Распечатайте значение S. Поскольку он инициализируется константой времени компиляции, это значение выпекается на сайте ссылок, печатая abc.
  • new Test() завершается, теперь выполняется инициализатор для I.

Урок: если вы полагаетесь на нетерпеливо инициализированные статические синглтоны, поместите объявление singleton в качестве последнего объявления статического поля или прибегайте к статическому блоку инициализатора, который возникает после всех других статических объявлений. Это приведет к тому, что класс будет полностью инициализирован единичному строительному коду.

Ответ 2

S - это константа времени компиляции, следуя правилам JLS 15.28. Поэтому любое появление S в коде заменяется значением, которое известно во время компиляции.

Если вы измените тип I на int, вы тоже увидите то же самое.

Ответ 3

У вас странное поведение из-за типа данных Integer. Относительно JLS 12.4.2 статические поля инициализируются в том порядке, в котором вы его записываете, НО сначала инициализируются константы компиляции.

Если вы не используете тип оболочки Integer, но тип int, вы получите нужное поведение.

Ответ 4

Ваш Test компилируется в:

public class Test {

    public static final Test me;
    public static final Integer I;
    public static final String S = "abc";

    static {
        me = new Test();
        I = Integer.valueOf(4);
    }

    public Test() {
        System.out.println(I);
        System.out.println("abc");
    }

    public static Test getInstance() { return me; }

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

Как вы можете видеть, конструктор для Test вызывается до того, как инициализируется I. Вот почему он печатает "null" для I. Если вы должны были поменять порядок объявления для me и I, вы получите ожидаемый результат, потому что I будет инициализирован до вызова конструктора. Вы также можете изменить тип для I от Integer до int.

Поскольку 4 необходимо получить автобокс (т.е. завернутый в объект Integer), он не является константой времени компиляции и является частью блока статического инициализатора. Однако, если тип был int, число 4 было бы константой времени компиляции, поэтому его не нужно было бы явно инициализировать. Поскольку "abc" является константой времени компиляции, значение S выводится как ожидалось.

Если вы замените,

public static final String S = "abc";

с,

public static final String S = new String("abc");

Тогда вы заметите, что вывод S равен "null". Почему это происходит? По той же причине, почему I также выводит "null". Поля, подобные этим, которые имеют буквальные, постоянные значения (для не требуется автобоксинг, например String), атрибут "ConstantValue" при компиляции, что означает, что их значение может быть разрешено просто путем поиска в постоянный пул классов, без необходимости запускать какой-либо код.