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

Почему не конечная "локальная" переменная не может использоваться внутри внутреннего класса, и вместо этого может использоваться не конечное поле охватывающего класса?

Есть несколько тем в переполнении стека по ошибке компилятора Cannot refer to a non-final variable message inside an inner class defined in a different method, и решение "объявляет его окончательным, и все готово", но с этим теоретическим вопросом я хотел бы проверить, что является логической причиной, почему код не может компилироваться:

private void updateStatus(String message) {
    Runnable doUpdateStatus = new Runnable() {
         public void run() {
             /* do something with message */
         }
    }
    /* do something with doUpdateStatus, like SwingUtilities.invokeLater() */
}

(решение: объявить message как final), тогда как это делает:

private String enclosingClassField;
private void updateStatus() {
    Runnable doUpdateStatus = new Runnable() {
         public void run() {
             /* do something with enclosingClassField */
         }
    }
    /* do something with doUpdateStatus, like SwingUtilities.invokeLater() */
}

Я действительно смущен. enclosingClassField не является окончательным, он может меняться каждый раз много раз, тогда как слабый аргумент message updateStatus может меняться только внутри его тела метода и вместо этого обвиняется компилятором;)

Даже ошибка компилятора вводит меня в заблуждение. Cannot refer to a non-final variable message inside an inner class defined in a different method: В отличие от чего? Разве не message определяется тем же методом, что и внутренний класс? Разве не enclosingClassField определяется вне метода? Эмм...

Может ли кто-нибудь указать мне на правильную интерпретацию этого вопроса? Спасибо.

4b9b3361

Ответ 1

Разница между локальными (методными) переменными vs переменными-членами класса. Член-переменная существует во время жизни объекта-объекта, поэтому на него может ссылаться внутренний экземпляр класса. Однако локальная переменная существует только во время вызова метода и по-разному обрабатывается компилятором тем, что неявная копия его генерируется как член внутреннего класса. Не объявляя конечную локальную переменную, ее можно было бы изменить, что привело к тонким ошибкам из-за того, что внутренний класс все еще ссылался на исходное значение этой переменной.

Обновление: Информационный бюллетень 25 специалистов Java обсуждает это более подробно.

Даже ошибка компилятора вводит меня в заблуждение. Cannot refer to a non-final variable message inside an inner class defined in a different method: В отличие от чего?

Из внутреннего метода класса run, который я считаю.

Ответ 2

Причина в том, что Java не поддерживает закрытия. Нет никаких команд JVM для доступа к локальной переменной вне метода, тогда как поля класса могут быть легко доступны из любого места.

Итак, когда вы используете локальную переменную final во внутреннем классе, компилятор фактически передает значение этой переменной в конструктор внутреннего класса. Очевидно, что это не будет работать для переменных не final, так как они могут измениться после построения внутреннего класса.

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

Ответ 3

три типа вещей: переменные экземпляра, локальные переменные и объекты:

■ Instance variables and objects live on the heap.
■ Local variables live on the stack.

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

поскольку использовать локальные переменные метода - локальные переменные метода хранятся в стеке и теряются, как только метод заканчивается.

Но даже после завершения метода локальный объект внутреннего класса может оставаться в куче. Метод локального внутреннего класса может по-прежнему использовать локальные переменные, отмеченные как final.

конечная переменная JVM принимает их как константу, поскольку они не будут меняться после запуска. И когда внутренний класс пытается получить к ним доступ, компилятор создает копию этой переменной в кучу и создает синтетическое поле внутри внутреннего класса, поэтому даже когда выполнение метода завершено, оно доступно, потому что внутренний класс имеет свою собственную копию.

заполняется синтетическое поле, которое на самом деле не существует в исходном коде, но компилятор создает эти поля в некоторых внутренних классах, чтобы сделать это поле доступным.

Ответ 4

Значение, которое вы используете, должно быть окончательным, но необязательные поля конечной ссылки могут быть изменены. Примечание: this неявно является окончательной ссылкой. Вы не можете его изменить.

private String enclosingClassField;
private void updateStatus() {
    final MutableClass ms = new MutableClass(1, 2);
    Runnable doUpdateStatus = new Runnable() {
         public void run() {
             // you can use `EnclosingClass.this` because its is always final
             EnclosingClass.this.enclosingClassField = "";
             // shorthand for the previous line.
             enclosingClassField = "";
             // you cannot change `ms`, but you can change its mutable fields.
             ms.x = 3;
         }
    }
    /* do something with doUpdateStatus, like SwingUtilities.invokeLater() */
}