Почему внутренние классы 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 внутри внутреннего класса, определенного другим методом".

Почему для анонимного внутреннего класса требуется, чтобы переменная экземпляра внешних классов была окончательной, чтобы получить к ней доступ?

Ответ 1

Хорошо, сначала позвольте всем расслабиться, и, пожалуйста, поставьте это оружие.

OK. Теперь причина, на которой настаивает язык, заключается в том, что она обманывает, чтобы предоставить вашим внутренним функциям класса доступ к локальным переменным, которых они жаждут. Среда выполнения создает копию локального контекста выполнения (и т.д.) И, таким образом, настаивает на том, что вы делаете все final, чтобы он мог держать вещи честными.

Если это не так, то код, который изменил значение локальной переменной после того, как был создан ваш объект, но до запуска внутренней функции класса, может быть запутанным и странным.

В этом суть многих брухаха вокруг Java и "закрытия".


Примечание: начальный абзац был шуткой по отношению к некоторому тексту целиком в исходной композиции OP.

Ответ 2

Методы в анонимном классе действительно не имеют доступа к местным переменных и параметров метода. Скорее, когда объект анонимный класс создается, копии окончательного локального переменные и параметры метода на которые ссылаются объектные методы сохраняются как переменные экземпляра в объект. Методы в объекте анонимного класса действительно доступ эти скрытые переменные экземпляра. [1]

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

[1] http://www.developer.com/java/other/article.php/3300881/The-Essence-of-OOP-using-Java-Anonymous-Classes.htm

Ответ 3

Причина в том, что Java не полностью поддерживает так называемые "Closures" - в этом случае final не понадобится - но вместо этого нашел трюк, позволив компилятору сгенерировать некоторые скрытые переменные, которые используются для предоставления функциональность, которую вы видите.

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

Это элегантное решение для обеспечения функциональности, не отступая от этого языка назад.


Изменить: для Java 8 lambdas дают более сжатый способ сделать то, что ранее было сделано с анонимными классами. Ограничения на переменные также уменьшились с "окончательного" до "по существу окончательного" - вам не нужно объявлять его окончательным, но если он рассматривается как окончательный (вы можете добавить последнее ключевое слово, и ваш код все равно будет компилироваться) может быть использован. Это действительно приятное изменение.

Ответ 5

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

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