Видимость локальной переменной Java в анонимных внутренних классах - почему требуется ключевое слово "final"?
Я не понимаю, почему я не всегда могу получить доступ к переменной изнутри "слушателя" или "обработчика".
Это мой код:
Button btnDownload = new Button(myparent, SWT.NONE);
btnDownload.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
btnDownload.setEnabled(false); // I CAN'T
}
});
Единственный способ - объявить его с помощью ключевого слова final
:
final Button btnDownload = new Button(myparent, SWT.NONE);
Почему мне нужно объявить переменную final для получения доступа внутри события?
Ответы
Ответ 1
Ваш SelectionAdapter
является анонимным внутренним классом, и я думаю, что это дает понять:
Локальные классы могут наиболее точно ссылаться на переменные экземпляра. Причина, по которой они не могут ссылаться на не конечные локальные переменные, заключается в том, что экземпляр локального класса может оставаться в памяти после возвращения метода. Когда метод возвращает локальные переменные, выходит за пределы области видимости, поэтому требуется их копирование. Если переменные werent final, то копия переменной в методе могла бы измениться, а копия в локальном классе didnt, поэтому они не синхронизированы.
Анонимные внутренние классы требуют конечных переменных из-за того, как они реализованы в Java. Анонимный внутренний класс (AIC) использует локальные переменные, создавая поле частного экземпляра, в котором хранится копия значения локальной переменной. Внутренний класс фактически не использует локальную переменную, а копию. На данном этапе должно быть довольно очевидно, что "Bad Thing" ™ может произойти, если изменяется исходное значение или скопированное значение; возникнут некоторые неожиданные проблемы синхронизации данных. Чтобы предотвратить такую проблему, Java требует, чтобы вы отметили локальные переменные, которые будут использоваться AIC как окончательные (то есть, неизменяемые). Это гарантирует, что внутренние копии экземпляров локальных переменных всегда будут соответствовать фактическим значениям.
Ответ 2
Я считаю, что Tom говорит, что если вы можете использовать локальные переменные в анонимном классе, то какую кнопку он должен включить в следующем сегменте кода.
Button btnDownload = new Button(myparent, SWT.NONE);
btnDownload.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
btnDownload.setEnabled(false); // I CAN'T
}
});
btnDownload = new Button(somethingelse , SWT.NONE);
btnDownload = null ;
Создатели java хотели избежать этой дискуссии, требуя, чтобы локальные переменные, используемые в анонимных классах, были окончательными.
Ответ 3
Вероятно, это не оптимальный выбор дизайна. В Java 8, наряду с выражением лямбда, это требование final
, надеюсь, будет удалено.
Цель состоит в том, чтобы запретить присвоение локальной переменной из анонимного класса. Но это не требует обозначения локальной переменной как окончательной.
void f()
Object var = ...;
new Anon(){
...
print(var); // reading is ok
var = x; // error, can't write to var [1]
}
Компилятор фактически создает копию var и сохраняет ее в анонимном классе. Анонимный класс получает доступ только к копии. Приведенный выше код фактически преобразуется в
void f()
Object var = ...;
new Anon$1(var);
class Anon$1
{
final Object $var;
Anon$1(Object var){ this.$var=var; }
...
print($var);
$var = x; // error, naturally [2]
}
Как вы можете видеть, нет никаких технических причин требовать, чтобы var
был окончательным. Весь компилятор должен делать, когда он сталкивается с [2], зная, что $var
является синтезированным полем для var
, сообщает об ошибке "Локальная переменная var не может быть назначена анонимным классом" (вместо "$ var является окончательной и не может быть назначено на" )
Разработчики языка решили раздражать нас, требуя ключевое слово final
для локальной переменной; Я не помню логики; в общем, Java не боялась многословия, если требуется ясность.
Ответ 4
В этом сообщении объясняется, почему
Переменные Java-метода и анонимный класс