tl; dr
Для полей , int b = b + 1является незаконным , поскольку bнелегальной вперед ссылка b. Вы можете исправить это, написавint b = this.b + 1 , что компилируется без нареканий.
Для локальных переменных , int d = d + 1является незаконным , поскольку dне инициализируется перед использованием. Это не относится к полям, которые всегда инициализируются по умолчанию.
Вы можете увидеть разницу, попытавшись скомпилировать
int x = (x = 1) + x;
как объявление поля и как объявление локальной переменной. Первое не удастся, но второе удастся из-за разницы в семантике.
Введение
Во-первых, правила для инициализаторов полей и локальных переменных очень разные. Итак, в этом ответе правила разбиты на две части.
Мы будем использовать эту тестовую программу повсюду:
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
Объявление bнедействительно и завершается illegal forward referenceошибкой.
Объявление dнедействительно и не выполняется сvariable d might not have been initialized ошибкой.
Тот факт, что эти ошибки разные, должен указывать на то, что причины ошибок также разные.
Поля
Инициализаторы полей в Java регулируются JLS §8.3.2 , Инициализация полей.
Объем поля определяется в JLS §6.3 , Область декларации.
Соответствующие правила:
- Объем объявления члена
m объявленного или унаследованного от типа класса C (§8.1.6), - это все тело C, включая любые объявления вложенных типов.
- Выражения инициализации для переменных экземпляра могут использовать простое имя любой статической переменной, объявленной в классе или унаследованной им, даже той, объявление которой текстуально происходит позже.
- Использование переменных экземпляра, объявления которых появляются в текстовом виде после использования, иногда ограничивается, даже если эти переменные экземпляра находятся в области видимости. См. §8.3.2.3 для точных правил, регулирующих прямую ссылку на переменные экземпляра.
В §8.3.2.3 говорится:
Объявление члена должно появиться в текстовом виде перед его использованием, только если член является экземпляром (соответственно статическим) полем класса или интерфейса C и выполняются все следующие условия:
- Использование происходит в экземпляре (соответственно статическом) инициализаторе переменной C или в экземпляре (соответственно статическом) инициализаторе C.
- Использование не находится в левой части задания.
- Использование происходит через простое имя.
- C - это самый внутренний класс или интерфейс, охватывающий использование.
Фактически вы можете ссылаться на поля до того, как они были объявлены, за исключением некоторых случаев. Эти ограничения предназначены для предотвращения кода вроде
int j = i;
int i = j;
от компиляции. В спецификации Java говорится, что «указанные выше ограничения предназначены для перехвата во время компиляции циклических или иным образом искаженных инициализаций».
К чему на самом деле сводятся эти правила?
Короче говоря, правила в основном говорят, что вы должны объявить поле перед ссылкой на это поле, если (а) ссылка находится в инициализаторе, (б) ссылка не назначается, (в) ссылка является простое имя (без таких квалификаторов this.) и (d) к нему нет доступа из внутреннего класса. Таким образом, прямая ссылка, удовлетворяющая всем четырем условиям, является недопустимой, но прямая ссылка, которая не работает хотя бы по одному условию, является допустимой.
int a = a = 1;компилируется , потому что он нарушает (б): ссылка a будет быть назначена, так что это законно , чтобы обратиться к aзаранее a«s полной декларации.
int b = this.b + 1также компилируется, потому что нарушает (c): ссылка this.bне является простым именем (оно дополнено this.). Эта странная конструкция по-прежнему четко определена, поскольку this.bимеет нулевое значение.
Итак, в основном, ограничения на ссылки на поля в инициализаторах препятствуют int a = a + 1успешной компиляции.
Заметим , что объявление поля int b = (b = 1) + bбудет не в состоянии компиляции, так как окончательный bпо - прежнему является незаконным опережающей ссылкой.
Локальные переменные
Объявления локальных переменных регулируются JLS §14.4 , Заявления объявления локальных переменных.
Сфера локальной переменной определяется в JLS §6.3 , Область декларации:
- Объем объявления локальной переменной в блоке (§14.4) - это остальная часть блока, в котором появляется объявление, начиная со своего собственного инициализатора и включая любые дальнейшие деклараторы справа в операторе объявления локальной переменной.
Обратите внимание, что инициализаторы находятся в пределах объявляемой переменной. Так почему не int d = d + 1;компилируется?
Причина заключается в правиле Java об определенном назначении ( JLS §16 ). Определенное присвоение в основном говорит о том, что каждый доступ к локальной переменной должен иметь предшествующее присвоение этой переменной, а компилятор Java проверяет циклы и ветки, чтобы гарантировать, что присвоение всегда происходит до любого использования (вот почему определенному назначению посвящен целый раздел спецификации к нему). Основное правило:
- Для каждого доступа к локальной переменной или пустой конечного поля
x, xдолжны быть определенно присвоенной перед въездом, или происходит ошибка времени компиляции.
В int d = d + 1;, доступ к dразрешен для локальной переменной штраф, но, поскольку dне был назначен до dобращения, компилятор выдает ошибку. В int c = c = 1, c = 1происходит во- первых, который назначает c, а затем cинициализируется в результате этого присваивания (который является 1).
Обратите внимание, что из-за определенных правил присваивания объявление локальной переменной int d = (d = 1) + d; будет успешно скомпилировано (в отличие от объявления поля int b = (b = 1) + b), потому что dоно определенно присваивается к моменту достижения финала d.
staticв переменную области класса, напримерstatic int x = x + 1;, получите ли вы ту же ошибку? Потому что в C # есть разница, статическая она или нестатическая.