Почему внутренние классы Java требуют "окончательных" переменных внешнего экземпляра?
final JTextField jtfContent = new JTextField();
btnOK.addActionListener(new java.awt.event.ActionListener(){
public void actionPerformed(java.awt.event.ActionEvent event){
jtfContent.setText("I am OK");
}
} );
Если я опущу final
, Я вижу ошибку "Невозможно обратиться к неконечной переменной jtfContent внутри внутреннего класса, определенного в другом методе".
Почему анонимный внутренний класс должен требовать, чтобы переменная экземпляра внешних классов была конечной, чтобы получить к ней доступ?
5 ответов
Ну, во-первых, давайте все расслабимся, и, пожалуйста, опусти пистолет.
ХОРОШО. Теперь причина, по которой язык настаивает на этом, заключается в том, что он обманывает, чтобы предоставить вашим функциям внутреннего класса доступ к локальным переменным, которые они жаждут. Среда выполнения создает копию локального контекста выполнения (и т. Д. В зависимости от ситуации), и поэтому настаивает на том, чтобы вы сделали все final
так что это может держать вещи честно.
Если этого не произошло, то код, который изменил значение локальной переменной после того, как ваш объект был сконструирован, но до запуска функции внутреннего класса, может быть странным и странным.
В этом суть многих недоразумений вокруг Java и "замыканий".
Примечание: вступительный абзац был шуткой в отношении некоторого текста с заглавными буквами в оригинальной композиции ФП.
Методы в анонимном классе на самом деле не имеют доступа к локальным переменным и параметрам метода. Скорее, когда создается экземпляр объекта анонимного класса, копии окончательных локальных переменных и параметров методов, на которые ссылаются методы объекта, сохраняются как переменные экземпляра в объекте. Методы в объекте анонимного класса действительно получают доступ к этим скрытым переменным экземпляра. [1]
Таким образом, локальные переменные и параметры методов, к которым обращаются методы локального класса, должны быть объявлены как final, чтобы предотвратить изменение их значений после создания экземпляра объекта.
Переменные вокруг определения класса живут в стеке, поэтому они, вероятно, исчезают, когда выполняется код внутри внутреннего класса (если вы хотите знать почему, ищите стек и кучу). Вот почему внутренние классы на самом деле не используют переменные в содержащем методе, а создаются с их копиями.
Это означает, что если вы измените переменную в содержащем методе после создания внутреннего класса, ее значение не изменится во внутреннем классе, даже если вы ожидаете этого. Чтобы избежать путаницы, Java требует, чтобы они были окончательными, поэтому вы не сможете изменить их.
Причина в том, что Java не полностью поддерживает так называемые "замыкания" - в этом случае final
не было бы необходимости - но вместо этого нашел хитрость, позволив компилятору генерировать некоторые скрытые переменные, которые используются для предоставления функциональности, которую вы видите.
Если вы разберете сгенерированный байт-код, вы увидите, как это делает компилятор, включая скрытые переменные со странным именем, содержащие копии конечных переменных.
Это элегантное решение, обеспечивающее функциональность без изменения языка.
Редактировать: Для Java 8 лямбда-выражения дают более краткий способ сделать то, что ранее было сделано с анонимными классами. Ограничения на переменные также уменьшились с "конечного" до "по существу окончательного" - вам не нужно объявлять его окончательным, но если он обрабатывается как конечный (вы можете добавить ключевое слово final, и ваш код все равно будет компилироваться) может быть использован. Это действительно приятное изменение.
Поскольку Java 8 final модификатор не является обязательным для внешних переменных экземпляра. Значение должно быть "эффективно окончательным". См. Ответ Разница между окончательным и фактически окончательным.