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 # есть разница, статическая она или нестатическая.