Почему этот код Java компилируется?

В области метода или класса строка ниже компилируется (с предупреждением):

int x = x = 1;

В области класса , где переменные получают значения по умолчанию, следующее дает ошибку undefined reference:

int x = x + 1;

Разве это не первый x = x = 1, который должен получить такую ​​же ошибку 'undefined reference'? Или, может быть, вторая строка int x = x + 1 должна скомпилироваться? Или что-то мне не хватает?

Ответ 1

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.3.3 для точных правил, регулирующих прямую ссылку на переменные экземпляра.

В §8.3.2.3 говорится:

Объявление участника должно появиться текстовым, прежде чем оно используется, только если элемент является экземпляром (соответственно статическим) полем класс или интерфейс C и выполняются все следующие условия:

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

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

int j = i;
int i = j;

от компиляции. Спецификация Java говорит, что "вышеописанные ограничения предназначены для улавливания, во время компиляции, циклических или иначе искаженных инициализаций".

Что на самом деле эти правила сводятся к?

Вкратце, правила в основном говорят, что вы должны объявить поле перед ссылкой на это поле, если (a) ссылка находится в инициализаторе, (b) ссылка не назначается, (c) ссылка - это простое имя (без классификаторов типа this.) и (d) он не доступен из внутреннего класса. Таким образом, прямая ссылка, которая удовлетворяет всем четырем условиям, является незаконной, но обратная ссылка, которая не работает, по крайней мере, при одном условии, является ОК.

int a = a = 1; компилируется, потому что он нарушает (b): присваивается ссылка a, поэтому он имеет право ссылаться на a перед a полным объявлением.

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.

Ответ 2

int x = x = 1;

эквивалентно

int x = 1;
x = x; //warning here

а в

int x = x + 1; 

сначала нам нужно вычислить x+1, но значение x неизвестно, поэтому вы получаете ошибку (компилятор знает, что значение x неизвестно)

Ответ 3

Это примерно эквивалентно:

int x;
x = 1;
x = 1;

Во-первых, int <var> = <expression>; всегда эквивалентно

int <var>;
<var> = <expression>;

В этом случае ваше выражение x = 1, которое также является выражением. x = 1 является допустимым оператором, поскольку var x уже объявлен. Это также выражение со значением 1, которое затем снова присваивается x.

Ответ 4

В java или на любом современном языке назначение происходит справа.

Предположим, что у вас есть две переменные x и y,

int z = x = y = 5;

Этот оператор действителен, и именно так компилятор разбивает их.

y = 5;
x = y;
z = x; // which will be 5

Но в вашем случае

int x = x + 1;

Компилятор дал исключение, потому что он разбивается так.

x = 1; // oops, it isn't declared because assignment comes from the right.

Ответ 5

В int x = x + 1; вы добавляете 1 в x, так что это значение x, оно еще не создано.

Но в int x=x=1; будет компилироваться без ошибок, потому что вы назначаете 1 для x.

Ответ 6

int x = x = 1; не равно:

int x;
x = 1;
x = x;

javap снова помогает нам, это инструкция JVM, сгенерированная для этого кода:

0: iconst_1    //load constant to stack
1: dup         //duplicate it
2: istore_1    //set x to constant
3: istore_1    //set x to constant

больше нравится:

int x = 1;
x = 1;

Здесь нет причин бросать опорную ошибку undefined. В настоящее время используется переменная до ее инициализации, поэтому этот код полностью соответствует спецификации. Фактически нет использования переменной вообще, просто назначений. И JIT-компилятор будет идти еще дальше, он устранит такие конструкции. Говоря честно, я не понимаю, как этот код связан с спецификацией JLS для инициализации и использования переменных. Нет проблем без проблем.;)

Пожалуйста, исправьте, если я ошибаюсь. Я не могу понять, почему другие ответы, которые относятся ко многим параграфам JLS, собирают так много плюсов. Эти параграфы не имеют ничего общего с этим случаем. Всего два последовательных назначения и не более.

Если мы напишем:

int b, c, d, e, f;
int a = b = c = d = e = f = 5;

равно:

f = 5
e = 5
d = 5
c = 5
b = 5
a = 5

Правильное выражение просто присваивается переменным один за другим, без какой-либо рекурсии. Мы можем использовать переменные любым способом:

a = b = c = f = e = d = a = a = a = a = a = e = f = 5;

Ответ 7

Ваша первая часть кода содержит второй = вместо плюса. Это скомпилируется в любом месте, в то время как вторая часть кода не будет компилироваться в любом месте.

Ответ 8

Во втором фрагменте кода x используется перед его объявлением, тогда как в первом фрагменте кода он просто назначается дважды, что не имеет смысла, но действительно.

Ответ 9

Позвольте разбить его шаг за шагом, правый ассоциативный

int x = x = 1

x = 1, назначьте 1 переменной x

int x = x, назначьте то, что x для себя, как int. Поскольку x ранее был назначен как 1, он сохраняет 1, хотя и избыточным образом.

Это компилируется отлично.

int x = x + 1

x + 1, добавьте его к переменной x. Однако x является undefined, что приведет к ошибке компиляции.

int x = x + 1, таким образом, эта строка компилирует ошибки, так как правая часть равенств не будет компилировать, добавляя одну к неназначенной переменной

Ответ 10

Второй int x=x=1 компилируется, потому что вы присваиваете значение x, но в другом случае int x=x+1 здесь переменная x не инициализируется, помните, что в java локальная переменная не инициализируется значением по умолчанию. Примечание. Если он (int x=x+1) в области видимости класса также тогда также даст ошибку компиляции, так как переменная не будет создана.

Ответ 11

int x = x + 1;

успешно компилируется в Visual Studio 2008 с предупреждением

warning C4700: uninitialized local variable 'x' used`

Ответ 12

x не инициализируется в x = x + 1;.

Язык программирования Java является статически типизированным, что означает, что все переменные должны быть сначала объявлены до их использования.

См.

Ответ 13

Строка кода не компилируется с предупреждением из-за того, как код действительно работает. Когда вы запускаете код int x = x = 1, Java сначала создает переменную x, как определено. Затем выполняется код назначения (x = 1). Поскольку x уже определен, система не имеет ошибок, устанавливая x в 1. Это возвращает значение 1, потому что теперь это значение x. Поэтому x теперь окончательно устанавливается как 1. Java в основном выполняет код, как будто это было:

int x;
x = (x = 1); // (x = 1) returns 1 so there is no error

Однако в вашей второй части кода int x = x + 1 оператор + 1 требует x, который должен быть определен, который к тому времени это не так. Поскольку операторы присваивания всегда означают, что код справа от = выполняется первым, код будет терпеть неудачу, потому что x - undefined. Java будет запускать код следующим образом:

int x;
x = x + 1; // this line causes the error because `x` is undefined

Ответ 14

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