Pourquoi seules les variables finales sont-elles accessibles en classe anonyme?

  1. a peut seulement être final ici. Pourquoi? Comment puis-je réaffecter a onClick() in onClick() sans la garder en tant que membre privé?

     private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; } }); } 
  2. Comment puis-je retourner le 5 * a quand il a cliqué? Je veux dire,

     private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; return b; // but return type is void } }); } 

    Comme noté dans les commentaires, une partie de ceci devient sans intérêt dans Java 8, où final peut être implicite. Cependant, seule une variable finale efficace peut être utilisée dans une classe interne anonyme ou une expression lambda.


    C’est essentiellement dû à la façon dont Java gère les fermetures .

    Lorsque vous créez une instance d’une classe interne anonyme, les valeurs utilisées dans cette classe sont copiées via le constructeur généré automatiquement. Cela évite au compilateur de générer automatiquement divers types supplémentaires pour contenir l’état logique des “variables locales”, comme le fait par exemple le compilateur C # (lorsque C # capture une variable dans une fonction anonyme, elle capture vraiment la variable – le La fermeture peut mettre à jour la variable d’une manière qui est vue par le corps principal de la méthode et vice versa.)

    Comme la valeur a été copiée dans l’instance de la classe interne anonyme, cela semblerait étrange si la variable pouvait être modifiée par le rest de la méthode – vous pourriez avoir du code qui semble fonctionner avec une variable obsolète ( parce que c’est effectivement ce qui se produirait … vous travailleriez avec une copie prise à un autre moment. De même, si vous pouviez apporter des modifications dans la classe interne anonyme, les développeurs pourraient s’attendre à ce que ces modifications soient visibles dans le corps de la méthode englobante.

    Rendre la variable finale supprime toutes ces possibilités – comme la valeur ne peut pas être modifiée du tout, vous n’avez pas à vous soucier de savoir si de telles modifications seront visibles. La seule manière d’autoriser la méthode et la classe interne anonyme à voir les modifications de l’autre consiste à utiliser un type de description modifiable. Cela pourrait être la classe englobante elle-même, un tableau, un type de wrapper mutable … n’importe quoi comme ça. En gros, c’est un peu comme communiquer entre une méthode et une autre: les modifications apscopes aux parameters d’une méthode ne sont pas visibles par l’appelant, mais les modifications apscopes aux objects auxquels les parameters font référence sont visibles.

    Si vous êtes intéressé par une comparaison plus détaillée entre les fermetures Java et C #, j’ai un article qui va plus loin. Je voulais me concentrer sur le côté Java dans cette réponse 🙂

    Il existe une astuce qui permet aux classes anonymes de mettre à jour les données dans la scope externe.

     private void f(Button b, final int a) { final int[] res = new int[1]; b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { res[0] = a * 5; } }); // But at this point handler is most likely not executed yet! // How should we now res[0] is ready? } 

    Cependant, cette astuce n’est pas très bonne en raison des problèmes de synchronisation. Si le gestionnaire est appelé plus tard, vous devez 1) synchroniser l’access à res si le gestionnaire a été appelé à partir du thread différent 2) avoir besoin d’une sorte d’indicateur ou d’indication que res a été mis à jour

    Cette astuce fonctionne bien, cependant, si la classe anonyme est appelée dans le même thread immédiatement. Comme:

     // ... final int[] res = new int[1]; Runnable r = new Runnable() { public void run() { res[0] = 123; } }; r.run(); System.out.println(res[0]); // ... 

    Une classe anonyme est une classe interne et la règle ssortingcte s’applique aux classes internes (JLS 8.1.3) :

    Toute variable locale, paramètre de méthode formelle ou paramètre de gestionnaire d’exception utilisé mais non déclaré dans une classe interne doit être déclaré final . Toute variable locale utilisée mais non déclarée dans une classe interne doit être définitivement atsortingbuée avant le corps de la classe interne .

    Je n’ai pas encore trouvé de raison ni d’explication sur jls ou jvms, mais nous soaps que le compilateur crée un fichier de classe distinct pour chaque classe interne et qu’il doit s’assurer que les méthodes déclarées sur ce fichier de classe ( au niveau du code d’octet) ont au moins access aux valeurs des variables locales.

    ( Jon a la réponse complète – je garde celle-ci non supprimée car on pourrait s’intéresser à la règle JLS)

    Vous pouvez créer une variable de niveau classe pour obtenir la valeur renvoyée. je veux dire

     class A { int k = 0; private void f(Button b, int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { k = a * 5; } }); } 

    maintenant vous pouvez obtenir la valeur de K et l’utiliser à votre guise.

    Réponse de votre pourquoi est:

    Une instance de classe interne locale est liée à la classe Main et peut accéder aux variables locales finales de sa méthode contenant. Lorsque l’instance utilise un local final de sa méthode contenant, la variable conserve la valeur qu’elle détenait au moment de la création de l’instance, même si la variable est hors de scope (il s’agit effectivement d’une version limitée de Java).

    Comme une classe interne locale n’est ni le membre d’une classe ni d’un package, elle n’est pas déclarée avec un niveau d’access. (Soyez clair, cependant, que ses propres membres ont des niveaux d’access comme dans une classe normale.)

    Eh bien, en Java, une variable peut être finale non seulement en tant que paramètre, mais en tant que champ de niveau classe, comme

     public class Test { public final int a = 3; 

    ou comme une variable locale, comme

     public static void main(Ssortingng[] args) { final int a = 3; 

    Si vous souhaitez accéder et modifier une variable à partir d’une classe anonyme, vous pouvez définir la variable comme variable de niveau classe dans la classe englobante .

     public class Test { public int a; public void doSomething() { Runnable runnable = new Runnable() { public void run() { System.out.println(a); a = a+1; } }; } } 

    Vous ne pouvez pas avoir une variable comme finale et lui donner une nouvelle valeur. final signifie juste que la valeur est immuable et finale.

    Et comme il est définitif, Java peut le copier en toute sécurité vers des classes anonymes locales. Vous n’obtenez pas de référence à l’int (d’autant plus que vous ne pouvez pas avoir de références à des primitives comme int en Java, juste des références à des objects ).

    Il ne fait que copier la valeur de a dans un int implicite appelé a dans votre classe anonyme.

    La raison pour laquelle l’access n’a été restreint qu’aux variables finales locales est que si toutes les variables locales étaient rendues accessibles, il faudrait d’abord les copier dans une section distincte où les classes internes pourraient y avoir access et conserver plusieurs copies de les variables locales mutables peuvent conduire à des données incohérentes. Considérant que les variables finales sont immuables et qu’un nombre quelconque d’exemplaires de ces variables n’a aucun impact sur la cohérence des données.

    Les méthodes au sein d’une classe interne anonome peuvent être appelées bien après que le thread qui l’a engendré s’est terminé. Dans votre exemple, la classe interne sera appelée sur le thread de répartition des événements et non sur le même thread que celui qui l’a créé. La scope des variables sera donc différente. Donc, pour protéger ces problèmes d’étendue d’affectation de variables, vous devez les déclarer définitifs.

    Lorsqu’une classe interne anonyme est définie dans le corps d’une méthode, toutes les variables déclarées finales dans la scope de cette méthode sont accessibles depuis la classe interne. Pour les valeurs scalaires, une fois affectée, la valeur de la variable finale ne peut plus être modifiée. Pour les valeurs d’object, la référence ne peut pas changer. Cela permet au compilateur Java de “capturer” la valeur de la variable au moment de l’exécution et de stocker une copie en tant que champ dans la classe interne. Une fois la méthode externe terminée et son cadre de stack supprimé, la variable d’origine a disparu, mais la copie privée de la classe interne persiste dans la mémoire de la classe.

    ( http://en.wikipedia.org/wiki/Final_%28Java%29 )

     private void f(Button b, final int a[]) { b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { a[0] = a[0] * 5; } }); } 

    Comme Jon a la réponse aux détails d’implémentation, une autre réponse possible serait que la JVM ne veuille pas gérer l’enregistrement en écriture qui a mis fin à son activation.

    Considérez le cas d’utilisation où votre lambda au lieu d’être appliqué est stocké à un endroit et exécuté plus tard.

    Je me souviens que dans Smalltalk, vous obtiendriez un magasin illégal lorsque vous effectuez une telle modification.

    Essayez ce code,

    Créez une liste de tableaux et mettez-la à l’intérieur de celle-ci et renvoyez-la:

     private ArrayList f(Button b, final int a) { final ArrayList al = new ArrayList(); b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; al.add(b); } }); return al; } 

    Peut-être que cette astuce donne une idée

     Boolean var= new anonymousClass(){ private Ssortingng myVar; //Ssortingng for example @Overriden public Boolean method(int i){ //use myVar and i } public Ssortingng setVar(Ssortingng var){myVar=var; return this;} //Returns self instane }.setVar("Hello").method(3);