385 votes

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

  1. a ne peuvent être finalisés qu'ici. Pourquoi ? Comment puis-je réaffecter a sur onClick() sans le garder comme 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 renvoyer le 5*a quand ça 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 
                }
            });
    }

521voto

Jon Skeet Points 692016

Comme indiqué dans les commentaires, une grande partie de ces éléments ne sont plus pertinents dans Java 8, où final peut être implicite.


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

Lorsque vous créez une instance d'une classe interne anonyme, toutes les variables qui sont utilisées dans cette classe ont leur valeurs copié via le constructeur autogénéré. Cela évite au compilateur de devoir autogénérer 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, il capture réellement la variable - 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 serait étrange que la variable puisse être modifiée par le reste de la méthode - vous pourriez avoir un code qui semble travailler avec une variable périmée (parce que c'est effectivement ce que la méthode serait vous travailleriez avec une copie prise à un moment différent). De même, si vous pouviez effectuer 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.

Le fait de rendre la variable finale élimine toutes ces possibilités : comme la valeur ne peut pas être modifiée du tout, vous n'avez pas à vous soucier de la visibilité de ces modifications. La seule façon de permettre à la méthode et à la classe interne anonyme de voir les changements de l'autre est d'utiliser un type mutable d'une certaine description. Il peut s'agir de la classe englobante elle-même, d'un tableau, d'un type enveloppant mutable... n'importe quoi de ce genre. En fait, c'est un peu comme la communication entre une méthode et une autre : les changements apportés à la classe interne anonyme sont visibles. paramètres d'une méthode ne sont pas vues par son appelant, mais les modifications apportées aux objets visé par les paramètres sont vus.

Si vous êtes intéressé par une comparaison plus détaillée entre les fermetures de Java et de C#, j'ai un article sur le sujet. article qui l'approfondit. Je voulais me concentrer sur le côté Java dans cette réponse :)

45voto

Ivan Dubrov Points 2596

Il existe une astuce qui permet aux classes anonymes de mettre à jour les données dans la portée 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 invoqué plus tard, vous devez 1) synchroniser l'accès à res si le gestionnaire a été invoqué depuis un autre thread 2) avoir une sorte de drapeau ou d'indication que res a été mis à jour.

Cette astuce fonctionne bien, cependant, si la classe anonyme est invoqué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]);
...

18voto

Andreas_D Points 64111

Une classe anonyme est une classe intérieure et la règle stricte s'applique à classes intérieures (JLS 8.1.3) :

Toute variable locale, tout paramètre de méthode formelle ou tout 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 affecté avant le corps de la classe interne .

Je n'ai pas encore trouvé de raison ou d'explication dans le jls ou le jvms, mais nous savons que le compilateur crée un fichier de classe séparé pour chaque classe interne et qu'il doit s'assurer que les méthodes déclarées dans ce fichier de classe (au niveau du byte code) ont au moins accès aux valeurs des variables locales.

( Jon a la réponse complète - Je garde celui-ci non supprimé car on pourrait être intéressé par la règle JLS)

11voto

com.status.live Points 1193

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

class A {
int k=0;
       private void f(IButton b, int a){
        b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            // TODO Auto-generated method stub
            k = a*5;
        }
        });
}

maintenant vous pouvez obtenir la valeur de K et l'utiliser où vous voulez.

La réponse à votre question est :

Une instance locale de classe interne est liée à la classe Main et peut accéder aux variables locales finales de la méthode qu'elle contient. Lorsque l'instance utilise une variable locale finale de la méthode qui la contient, la variable conserve la valeur qu'elle avait au moment de la création de l'instance, même si la variable est sortie de son champ d'application (il s'agit en fait de la version grossière et limitée des fermetures de Java).

Comme une classe interne locale n'est pas membre d'une classe ou d'un paquetage, elle n'est pas déclarée avec un niveau d'accès. (Il est clair, cependant, que ses propres membres ont des niveaux d'accès comme dans une classe normale).

6voto

Zach L Points 7261

Eh bien, en Java, une variable peut être finale non seulement en tant que paramètre, mais aussi 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(String[] args)
{
 final int a = 3;

Si vous voulez accéder à une variable et la modifier à partir d'une classe anonyme, vous pouvez faire de cette variable un objet de type au niveau de la classe dans le enfermant classe.

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 exactement cela : la valeur est immuable et définitive.

Et comme c'est définitif, Java peut sans risque copie à des classes anonymes locales. Tu ne vas pas obtenir référence à l'int (d'autant plus que vous ne pouvez pas avoir de références à des primitives comme int en Java, seulement des références à Objets ).

Elle copie simplement la valeur de a dans un int implicite appelé a dans votre classe anonyme.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X