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, поэтому мы четко сделали ссылку, зададим порядок и не обнаружили ошибку предупреждения или компиляции.

Является ли этот код законным?

Ответ 1

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

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

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

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

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

Ответ 2

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

static Integer j;
static final Integer i = j;

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

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

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

Ответ 3

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

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

    class StaticInitialization {

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

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

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

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

Ответ 4

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

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 имеет значение null. Однако i становится 5, когда вы ссылаетесь на него внутри Foo2. Заметим, что если i была константой компиляции (i is int not Integer), тогда j будет 5.

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

Но обращения к методам не проверяются одинаково.