Недопустимая обратная опорная ошибка для статических конечных полей
Я пытаюсь скомпилировать 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;
, класс принимается компилятором.
Как это можно объяснить? Является ли мое понимание прямого слова лексически ошибочным или это какой-то частный случай, о котором я не знаю?
Ответы
Ответ 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 - самый внутренний класс или интерфейс, охватывающий использование.
В статье Что такое прямое ссылочное правило? содержит отличное объяснение правил и ограничений, когда дело доходит до инициализации членов и прямой ссылки.