Avec les classes anonymes, vous déclarez en fait une classe imbriquée "sans nom". Pour les classes imbriquées, le compilateur génère une nouvelle classe publique autonome avec un constructeur qui prendra toutes les variables qu'il utilise comme arguments (pour les classes imbriquées "nommées", il s'agit toujours d'une instance de la classe d'origine / englobante). Cela est dû au fait que l'environnement d'exécution n'a aucune notion de classes imbriquées, il doit donc y avoir une conversion (automatique) d'une classe imbriquée vers une classe autonome.
Prenez ce code par exemple:
public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
// this is not valid, won't compile
System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Cela ne fonctionnera pas, car c'est ce que le compilateur fait sous le capot:
public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
La classe anonyme d'origine est remplacée par une classe autonome générée par le compilateur (le code n'est pas exact, mais devrait vous donner une bonne idée):
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
Comme vous pouvez le voir, la classe autonome contient une référence à l'objet partagé, rappelez-vous que tout en java est une valeur de passage, donc même si la variable de référence 'shared' dans EnclosingClass est modifiée, l'instance vers laquelle elle pointe n'est pas modifiée , et toutes les autres variables de référence pointant vers lui (comme celle de la classe anonyme: Enclosing $ 1), n'en seront pas conscientes. C'est la raison principale pour laquelle le compilateur vous oblige à déclarer ces variables «partagées» comme finales, afin que ce type de comportement ne soit pas intégré dans votre code déjà en cours d'exécution.
Maintenant, c'est ce qui se passe lorsque vous utilisez une variable d'instance à l'intérieur d'une classe anonyme (c'est ce que vous devez faire pour résoudre votre problème, déplacez votre logique vers une méthode "instance" ou un constructeur d'une classe):
public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared); // this is perfectly valid
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Cela compile très bien, car le compilateur modifiera le code, de sorte que la nouvelle classe générée Enclosing $ 1 contiendra une référence à l'instance de EnclosingClass où elle a été instanciée (ce n'est qu'une représentation, mais devrait vous permettre de continuer):
public void someMethod() {
new EnclosingClass$1(this).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}
public void run() {
System.out.println(enclosing.shared);
}
}
Comme ceci, lorsque la variable de référence «partagée» dans EnclosingClass est réaffectée, et cela se produit avant l'appel à Thread # run (), vous verrez «autre bonjour» imprimé deux fois, parce que maintenant la variable entourante EnclosingClass $ 1 # gardera une référence à l'objet de la classe où il a été déclaré, les modifications apportées à tout attribut de cet objet seront donc visibles par les instances de EnclosingClass $ 1.
Pour plus d'informations sur le sujet, vous pouvez voir cet excellent article de blog (non écrit par moi): http://kevinboone.net/java_inner.html