a
ne peut être définitif ici. Pourquoi? Comment puis - je réattribuera
enonClick()
méthode sans le 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; } }); }
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 } }); }
java
event-handling
anonymous-class
Andrii Abramov
la source
la source
Réponses:
Comme indiqué dans les commentaires, certains de ces éléments ne sont plus pertinents dans Java 8, où ils
final
peuvent être implicites. Cependant, seule une variable effectivement finale 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, toutes les variables utilisées dans cette classe ont leurs valeurs copiées via le constructeur généré automatiquement. Cela évite au compilateur d'avoir à générer automatiquement divers types supplémentaires pour conserver l'état logique des "variables locales", comme par exemple le compilateur C # le fait ... (Lorsque C # capture une variable dans une fonction anonyme, il 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, il semblerait étrange que la variable puisse être modifiée par le reste de la méthode - vous pourriez avoir du code qui semblait fonctionner avec une variable obsolète ( car c'est effectivement ce qui se passerait ... vous travaillez 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 ces changements seront visibles. La seule façon d'autoriser la méthode et la classe interne anonyme à voir les modifications de l'autre est d'utiliser un type mutable d'une description. Cela pourrait être la classe englobante elle-même, un tableau, un type d'emballage mutable ... quelque chose comme ça. Fondamentalement, c'est un peu comme communiquer entre une méthode et une autre: les modifications apportées aux paramètres d'une méthode ne sont pas visibles par son appelant, mais les modifications apportées aux objets référencés par les paramètres 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 :)
la source
final
(mais il doit toujours être effectivement final).Il existe une astuce qui permet à la classe anonyme de mettre à jour les données dans la portée externe.
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'accès à 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 une classe anonyme est immédiatement invoquée dans le même thread. Comme:
la source
List.forEach
code est sûr.Une classe anonyme est une classe interne et la règle stricte s'applique à classes internes (JLS 8.1.3) :
Je n'ai pas encore trouvé de raison ou d'explication sur les jls ou les jvms, mais nous savons 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 octet) ont au moins accès 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)
la source
Vous pouvez créer une variable de niveau classe pour obtenir la valeur renvoyée. je veux dire
vous pouvez maintenant obtenir la valeur de K et l'utiliser où vous le souhaitez.
La 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 conteneur. Lorsque l'instance utilise un local final de sa méthode conteneur, 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 portée (il s'agit en fait de la version brute et limitée de fermetures de Java).
Parce qu'une classe interne locale n'est ni le membre d'une classe ou d'un package, elle n'est pas déclarée avec un niveau d'accès. (Soyez clair, cependant, que ses propres membres ont des niveaux d'accès comme dans une classe normale.)
la source
Eh bien, en Java, une variable peut être finale non seulement en tant que paramètre, mais en tant que champ au niveau de la classe, comme
ou en tant que variable locale, comme
Si vous souhaitez accéder à une variable à partir d'une classe anonyme et la modifier, vous pouvez faire de la variable une variable de niveau classe dans la classe englobante .
Vous ne pouvez pas avoir une variable comme finale et lui donner une nouvelle valeur.
final
signifie juste cela: la valeur est immuable et définitive.Et comme il est définitif, Java peut le copier en toute sécurité dans des classes anonymes locales. Vous n'obtenez pas de référence à l'int (surtout que vous ne pouvez pas avoir de références à des primitives comme int en Java, juste des références à des objets ).
Il copie simplement la valeur de a dans un int implicite appelé a dans votre classe anonyme.
la source
static
. Il est peut-être plus clair si vous utilisez plutôt "variable d'instance".La raison pour laquelle l'accès a été limité uniquement aux variables finales locales est que si toutes les variables locales devaient être rendues accessibles, elles devraient d'abord être copiées dans une section distincte où les classes internes peuvent y avoir accès et conserver plusieurs copies de les variables locales mutables peuvent conduire à des données incohérentes. Alors que les variables finales sont immuables et que le nombre de copies qu'elles contiennent n'aura donc aucun impact sur la cohérence des données.
la source
Int
est en cours de réaffectation à l'intérieur d'une fermeture, changez cette variable en une instance deIntRef
(essentiellement unInteger
wrapper mutable ). Chaque accès variable est ensuite réécrit en conséquence.Pour comprendre la justification de cette restriction, envisagez le programme suivant:
L' interfaceInstance reste en mémoire après le retour de la méthode d' initialisation , mais pas le paramètre val . La machine virtuelle Java ne peut pas accéder à une variable locale en dehors de sa portée, donc Java effectue l'appel suivant à printInteger en copiant la valeur de val dans un champ implicite du même nom dans interfaceInstance . L' interfaceInstance aurait capturé la valeur du paramètre local. Si le paramètre n'était pas définitif (ou effectivement définitif), sa valeur pourrait changer, se désynchroniser avec la valeur capturée, provoquant potentiellement un comportement peu intuitif.
la source
Les méthodes au sein d'une classe interne anonome peuvent être invoquées bien après la fin du thread qui l'a engendrée. Dans votre exemple, la classe interne sera invoquée sur le thread de répartition d'événements et non dans le même thread que celui qui l'a créée. Par conséquent, la portée des variables sera différente. Donc, pour protéger ces problèmes de portée d'affectation de variables, vous devez les déclarer définitifs.
la source
Lorsqu'une classe interne anonyme est définie dans le corps d'une méthode, toutes les variables déclarées finales dans la portée de cette méthode sont accessibles à partir de la classe interne. Pour les valeurs scalaires, une fois affectée, la valeur de la variable finale ne peut pas changer. Pour les valeurs d'objet, 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 pile 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 )
la source
la source
Comme Jon a les détails de l'implémentation, une autre réponse possible serait que la JVM ne veut pas gérer les enregistrements en écriture qui ont mis fin à son activation.
Considérez le cas d'utilisation où vos lambdas au lieu d'être appliqués, sont stockés dans un endroit et exécutés plus tard.
Je me souviens que dans Smalltalk, vous obtiendrez un magasin illégal lorsque vous effectuez une telle modification.
la source
Essayez ce code,
Créez une liste de tableaux et mettez-y de la valeur et retournez-la:
la source
La classe anonyme Java est très similaire à la fermeture Javascript, mais Java l'implémente de manière différente. (vérifier la réponse d'Andersen)
Donc, afin de ne pas confondre le développeur Java avec le comportement étrange qui peut se produire pour ceux qui viennent de l'arrière-plan Javascript. Je suppose que c'est pourquoi ils nous obligent à utiliser
final
, ce n'est pas la limitation JVM.Regardons l'exemple Javascript ci-dessous:
En Javascript, la
counter
valeur sera 100, car il n'y en a qu'uncounter
variable du début à la fin.Mais en Java, s'il n'y en a pas
final
, il s'imprime0
, car pendant la création de l'objet interne, la0
valeur est copiée dans les propriétés cachées de l'objet de classe interne. (il y a deux variables entières ici, une dans la méthode locale, une autre dans les propriétés cachées de la classe interne)Donc, tout changement après la création de l'objet intérieur (comme la ligne 1), cela n'affectera pas l'objet intérieur. Cela créera donc une confusion entre deux résultats et comportements différents (entre Java et Javascript).
Je crois que c'est pourquoi, Java décide de le forcer à être définitif, de sorte que les données sont «cohérentes» du début à la fin.
la source
Variable finale Java à l'intérieur d'une classe interne
la classe interne ne peut utiliser que
Object
...)int
...) peut être encapsulé par un type de référence final.IntelliJ IDEA
peut vous aider à le convertir en un tableau d' élémentsLorsqu'un
non static nested
(inner class
) [À propos] est généré par le compilateur - une nouvelle classe -<OuterClass>$<InnerClass>.class
est créé et les paramètres liés sont passés au constructeur [Variable locale sur la pile] . C'est similaire à la fermeturela variable finale est une variable qui ne peut pas être réaffectée. la variable de référence finale peut toujours être modifiée en modifiant un état
Il était possible que ce soit bizarre car en tant que programmeur, vous pourriez faire comme ça
la source
Peut-être que cette astuce vous donne une idée
la source