web-dev-qa-db-fra.com

Pourquoi Java les classes internes nécessitent des variables d'instance externes "finales"?

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");
    }
} );

Si j'omets final, je vois l'erreur "Ne peut pas faire référence à une variable non finale jtfContent dans une classe interne définie dans une méthode différente".

Pourquoi une classe interne anonyme doit-elle exiger que la variable d'instance des classes externes soit finale pour y accéder?

51
3m masr

Eh bien, tout d'abord, détendons-nous, et s'il vous plaît, posez cette arme.

D'ACCORD. Maintenant, la raison pour laquelle le langage insiste là-dessus est qu'il triche afin de fournir à vos fonctions de classe interne l'accès aux variables locales dont elles ont besoin. Le runtime fait une copie du contexte d'exécution local (et etc. selon le cas), et donc il insiste pour que vous fassiez tout final pour qu'il puisse rester honnête.

Si ce n'est pas le cas, alors le code qui a changé la valeur d'une variable locale après la construction de votre objet mais avant la fonction de classe interne s'exécute peut être déroutant et bizarre.

C'est l'essence de beaucoup de brouhaha autour de Java et "fermetures").


Remarque: le premier paragraphe était une blague en référence à du texte en majuscules dans la composition originale de l'OP.

67
Pointy

Les méthodes d'une classe anonyme n'ont pas vraiment accès aux variables locales et aux paramètres de méthode. Au contraire, lorsqu'un objet de la classe anonyme est instancié, des copies des finales variables locales et des paramètres de méthode référencés par les méthodes de l'objet sont stockées en tant que variables d'instance dans l'objet. Les méthodes de l'objet de la classe anonyme accèdent vraiment à ces variables d'instance cachées. [1]

Ainsi, les variables locales et les paramètres de méthode accédés par les méthodes de la classe locale doivent être déclarés finaux pour empêcher leurs valeurs de changer après que l'objet a été instancié.

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

21
Jorge Ferreira

La raison en est que Java ne prend pas complètement en charge les soi-disant "fermetures" - auquel cas le final ne serait pas nécessaire - mais a plutôt trouvé une astuce en laissant le compilateur générer des variables cachées qui sont utilisées pour donner la fonctionnalité que vous voyez.

Si vous démontez le code d'octets généré, vous pouvez voir comment le compilateur le fait, y compris les variables cachées au nom étrange contenant des copies des variables finales.

C'est une solution élégante pour donner des fonctionnalités sans plier la langue en arrière pour le faire.


Edit: For Java 8 lambdas donnent un moyen plus concis de faire ce qui était précédemment fait avec les classes anonymes. Les restrictions sur les variables sont également relâchées de "final" à "essentiellement final" - vous ne le faites pas devez le déclarer final, mais s'il est traité comme il est final (vous pouvez ajouter le mot-clé final et votre code compilerait toujours), il peut être utilisé. C'est vraiment un changement sympa.

Les variables autour de la définition de la classe vivent sur la pile, donc elles ont probablement disparu lorsque le code à l'intérieur de la classe interne s'exécute (si vous voulez savoir pourquoi, recherchez la pile et le tas). C'est pourquoi les classes internes n'utilisent pas réellement les variables dans la méthode conteneur, mais sont construites avec des copies d'entre elles.

Cela signifie que si vous modifiez la variable dans la méthode conteneur après avoir construit la classe interne, sa valeur ne changera pas dans la classe interne, même si vous vous y attendez. Pour éviter toute confusion là-bas, Java exige qu'ils soient finaux, vous vous attendez donc à ne pas pouvoir les modifier.

7
Bart van Heukelom

Étant donné que Java 8 le modificateur final est facultatif pour les variables d'instance externes. La valeur doit être "effectivement finale". Voir la réponse - Différence entre final et effectivement final .

6
anstarovoyt