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

Рекурсивный инициализатор работает, когда я добавляю "this"?

Это не скомпилируется (с ошибкой illegal forward reference), как и следовало ожидать:

class test {
    int x = x + 42;
}

Но это работает:

class test {
    int x = this.x + 42;
}

Что происходит? Что назначается в последнем случае?

4b9b3361

Ответ 1

Сводка: оба инициализатора получают доступ к полю, которое еще не инициализировано (и, следовательно, по-прежнему имеет значение по умолчанию, равное нулю). Поскольку это, скорее всего, ошибка программирования, язык запрещает некоторые простые формы такого доступа. Однако это не запрещает более сложную форму.

Поведение совместимо с JLS, в частности §8.3.2.3. Ограничения на использование полей во время инициализации

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

  • Использование происходит в инициализаторе инициализатора экземпляра экземпляра (соответственно static) C или в экземпляре (соответственно static).

  • Использование не находится в левой части задания.

  • Использование осуществляется с помощью простого имени.

  • C - самый внутренний класс или интерфейс, охватывающий использование.

Первый пример удовлетворяет всем четырем условиям и поэтому недействителен. Второй пример не удовлетворяет третьему условию (this.x - не простое имя), и поэтому он ОК.

Общая последовательность событий следующая:

Таким образом, если инициализатор ссылается на поле, которое появляется позже в определении класса (или в самом поле), оно увидит значение по умолчанию этого другого поля. Вероятно, это будет ошибка программирования и поэтому явно запрещена в §8.3.2.3.

Если вы обходите §8.3.2.3, например, используя this. для пересылки в поле, вы увидите значение по умолчанию (ноль для int). Таким образом, четко определено и гарантировано установить x на 42:

class test {
    int x = this.x + 42;
}

Ответ 2

Слишком сложно обнаружить и запретить все обращения к x во время инициализации x. Например

int x = that().x;                |    int x = getX();
                                 |
Test that(){ return this; }      |    int getX(){ return x; }

Спектр останавливается при "доступе простым именем" и не пытается быть более полным.

В другом разделе "Определенное присвоение" спецификация делает аналогичную вещь. Например

public class Test
{
    static final int y;
    static final int z = y;  // fail, y is not definitely assigned 
    static{ y = 1; }
}

public class Test
{
    static final int y;
    static final int z = Test.y;  // pass... because it not a simple name
    static{ y = 1; }
}

Интересно, что "Определенное присвоение" конкретно упоминает, что this.x эквивалентно x

(или для поля, простое имя поля, присвоенного этим)

этот раздел может быть добавлен в раздел, цитируемый NPE.

  • использование осуществляется через простое имя (или простое имя, присвоенное этим)

Но в конце во время компиляции невозможно проанализировать все возможные способы использования/доступа к полю.

Ответ 3

В первом случае компилятор пытается оценить выражение "x + 42", но не получается, потому что x не инициализируется.

Во втором случае выражение 'this.x + 42' оценивается во время выполнения (из-за 'this' keyword), когда x уже инициализирован и имеет значение 0.