Java: почему нет предупреждения при ссылке на поле до его определения?

На статическое поле нельзя ссылаться до его определения или инициализации:

static Integer j = i; /* compile error */
static final Integer i = 5;

Однако, когда на него ссылаются из блока инициализации экземпляра (в анонимном внутреннем классе), даже не генерируется предупреждение.

Смотрите пример:

class StaticInitialization {

    static final Object o = new Object() {{
        j = i;
    }};

    static Integer j, k;
    static final Integer i = 5;

    static final Object o2 = new Object() {{
        k = i;
    }};
}

Результат: j == null, k == 5так ясно, что мы сделали ссылку, порядок имеет значение, а не предупреждение или ошибка компиляции.

Законен ли этот код?

6 ответов

Решение

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

Ограниченный анализ "объявлять перед ссылками" из других статов того же класса на самом деле является всего лишь помощником против наиболее распространенных явлений, а не железной гарантией от косвенных ошибок.

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

Расширение его для учета побочных эффектов создания объектов и вызовов методов, OTOH, потребовало бы в 20-1000 раз больший вес и объем статического анализа программ. Статический анализ требует доступа ко всему скомпилированному программному коду, с вычислениями на основе ограничений, чтобы определить, что может произойти, и временем выполнения, потенциально в минутах.

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

В первом блоке вы ссылаетесь не на статическое поле до его инициализации, а на ссылку до того, как оно будет определено (как вам скажет компилятор). Это будет работать, например:

static Integer j;
static final Integer i = j;

Все поля имеют значения по умолчанию, если они не установлены явно (для объектов это null ссылка, для примитивов, это соответствующее разумное значение по умолчанию, 0, false, так далее.). Поэтому поля всегда инициализируются, поэтому компилятору не нужно проверять это.

относительно final: это действительно модификатор, используемый в интересах разработчиков. Как утверждает JSL, final поля должны быть "определенно назначены" перед доступом. Это не означает, что в противном случае они не будут иметь никакой ценности (если бы не final), это просто означает, что компилятор защищает вас от явного присвоения, если он не может найти это назначение.

Итак, дляfinal поля, вы определенно можете ссылаться на них перед явным присваиванием. Просто вставьте фрагмент кода выше, чтобы проверить.

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

Это возможно, потому что есть довольно много ограничений на final переменные (что в значительной степени final).

Следовательно, углубленный анализ "назначено ли это перед использованием" действительно работает только в ограниченном мире конечных переменных.

Ваш код похож на этот:

class StaticInitialization
{
    static final Foo1 o = new Foo1();
    static Integer j, k;
    static final Integer i = 5;
    static final Foo2 o = new Foo2();

    class Foo1
    {
        public Foo1()
        {
            j = i;
        }
    }

    class Foo2
    {
        public Foo2()
        {
            k = i;
        }
    }
}

Когда вы ссылаетесь на i внутри Foo1, i был нулевым Тем не мение, i становится 5 когда вы ссылаетесь на это внутри Foo2, Обратите внимание, что если i была константа компиляции (i является int не Integer), затем j будет 5.

Посмотрите этот связанный вопрос: Создание объекта статическим способом

Это определено в JLS 8.3.2.3.

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

Вот почему вы получаете ошибку, когда вы делаете это..

static Integer j = i; /* compile error */
static final Integer i = 5;

Но доступ по методам не проверяется таким же образом.

Да, это происходит потому, что сначала выделяется пространство для всех переменных (и инициализируется с помощью #0), а после этого вся инициализация выполняется в порядке вашего кода.

Значит, если вы измените код следующим образом:

    class StaticInitialization {

    статическое целое число j, k;
    статическое окончательное целое число i = 5;

    статический конечный объект o = новый объект () {{
        j = я;
    }};

    статический конечный объект o2 = новый объект () {{
        k = i;
    }};
    }

Результат для всех variables == 5,

Другие вопросы по тегам