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

Недопустимая обратная опорная ошибка для статических конечных полей

Я пытаюсь скомпилировать Java-класс, который javac отклоняет с помощью недопустимой прямой ссылочной ошибки, где ссылка на нарушение является лексически после ссылочного поля. Следующий класс убирается как можно больше, показывая такое же поведение:

java.util.concurrent.Callable и многие применения Object используются только как заполнители для удаления ненужных фрагментов кода.

public class Test {
    static final Object foo = method(new java.util.concurrent.Callable<Object>() {
        @Override
        public Object call() throws Exception {
            return bar;
        }
    });

    static final Object bar = foo;

    static Object method(Object binder) {
        return null;
    }
}

При компиляции с использованием javac Test.java, javac выводит следующее сообщение об ошибке:

Test.java:9: illegal forward reference
    static final Object bar = foo;
                              ^

Таким образом, компилятор жалуется на объявление bar, ссылающееся на foo, в то время как foo должно находиться в области объявления bar. Но как только ссылка объявления bar в foo удаляется, например. путем изменения строки 5 от return bar; до return null;, класс принимается компилятором.

Как это можно объяснить? Является ли мое понимание прямого слова лексически ошибочным или это какой-то частный случай, о котором я не знаю?

4b9b3361

Ответ 1

Ваше понимание прямой ссылки верно. Ссылка на foo в строке 9 не является прямой ссылкой вообще, так как она не отображается по тексту перед ее объявлением (см. Определение того, что представляет собой прямую ссылку в разделе 8.3.2.3 из Спецификация языка Java).

Поведение, которое вы наблюдаете, является симптомом ошибки javac . См. этот отчет об ошибках. Проблема, похоже, исправлена ​​в более новых версиях компилятора, например. OpenJDK 7.

Он влияет только на прямые ссылки, используемые в качестве инициализаторов, на поля final. Похоже, что проблема затрагивает статические и нестатические поля.

Обратите внимание, что ссылка на bar в call() является легальной прямой ссылкой, поскольку она встречается внутри другого класса (см. примеры в разделе 8.3.2.3 Спецификация языка Java).

Также обратите внимание, что каждая из следующих изменений устраняет ошибку:

Создание bar нефинал:

static Object bar = foo;

Инициализация bar в статическом или инициализационном блоке инициализации:

static final Object bar;

static {
  bar = foo;
}

Также помогает перенос инициализации foo в блок инициализатора.

Инициализация bar из временной ссылки без окончаний на foo:

static Object tmp = foo;
static final Object bar = tmp;

Инициализация bar с помощью Test.foo (найдена Томом Андерсоном) или с this.foo в нестационарном случае:

static final Object bar = Test.foo;

Удаление bar и обращение к объекту с помощью foo внутри call():

static final Object foo = method(new java.util.concurrent.Callable<Object>() {
    @Override
    public Object call() throws Exception {
        return foo;
    }   
});

Ответ 2

Спецификации языка Java специально упоминает ограничения на поля объектов во время фазы инициализации, в частности (C - это интерфейс или класс):

При выполнении этих условий возникает ошибка времени компиляции для прямых ссылок:

  • Использование происходит в экземпляре (соответственно статическом) инициализаторе переменной C или в экземпляре (соответственно статическом) инициализаторе C.
  • Использование не находится в левой части задания.
  • Использование осуществляется с помощью простого имени.
  • C - самый внутренний класс или интерфейс, охватывающий использование.

В статье Что такое прямое ссылочное правило? содержит отличное объяснение правил и ограничений, когда дело доходит до инициализации членов и прямой ссылки.