Classes internes en Java
Déclarez des classes internes non statiques en Java qui conservent une référence implicite à une instance de la classe englobante.
Une classe interne est une classe imbriquée non statique — déclarée à l'intérieur d'une autre classe sans le modificateur static. Sa caractéristique principale : chaque instance d'une classe interne est liée à une instance de la classe englobante et porte une référence implicite vers elle. Depuis l'intérieur de la classe interne, vous pouvez lire et écrire les champs de l'instance externe et appeler ses méthodes comme s'ils vous appartenaient.
Cela fait des classes internes l'outil idéal lorsqu'une petite classe secondaire doit participer intimement à l'état d'une autre classe — notamment les itérateurs qui parcourent les données internes de leur conteneur.
Cette page explique comment déclarer une classe interne, comment en créer une (vous avez toujours besoin d'une instance externe), le qualificateur Outer.this, le cas d'utilisation canonique des itérateurs, le risque de fuite mémoire lié à la référence implicite, et comment choisir entre une classe interne et une classe imbriquée static.
Déclarer une classe interne
Omettez static dans la déclaration d'une classe imbriquée :
public class Outer {
private int x = 1;
class Inner { // no static — inner class
int get() { return x; } // reads Outer's x directly
}
}Inner n'a aucun champ propre et pourtant get() retourne 1. Le x nu est résolu en Outer.this.x via la référence implicite.
Créer une instance
Puisque chaque instance d'une classe interne est liée à une instance externe, vous avez besoin d'une instance externe pour en créer une. Il existe deux façons de faire :
Outer o = new Outer();
Outer.Inner i = o.new Inner(); // bind explicitly to o…ou depuis l'intérieur d'une méthode non statique de Outer :
public class Outer {
void demo() {
Inner i = new Inner(); // implicitly bound to this
}
}La syntaxe o.new Inner() est rare et surprenante — la plupart du code crée des instances de classes internes depuis les méthodes de la classe externe elle-même, où la liaison est implicite.
Outer.this — dépasser une collision de noms
Lorsque la classe interne déclare un champ portant le même nom que celui de la classe externe, le champ interne le masque. Pour accéder au champ externe, qualifiez-le avec Outer.this :
public class Outer {
int x = 1;
class Inner {
int x = 2;
void demo() {
System.out.println(x); // 2 — Inner's x
System.out.println(this.x); // 2 — Inner's x
System.out.println(Outer.this.x); // 1 — Outer's x
}
}
}this dans une classe interne fait référence à l'instance interne ; Outer.this fait référence à l'instance externe englobante.
Le cas d'utilisation canonique — les itérateurs
La raison classique d'utiliser une classe interne est l'implémentation d'un itérateur sur les données privées internes d'un conteneur :
public class IntList {
private int[] data;
private int size;
// ... constructors, add, ...
public Iterator<Integer> iterator() {
return new InnerIterator();
}
private class InnerIterator implements Iterator<Integer> {
private int i = 0;
public boolean hasNext() { return i < size; }
public Integer next() { return data[i++]; }
}
}InnerIterator accède directement à data et size via la référence externe implicite. Aucun setter, aucun accesseur nécessaire — la classe interne fait partie de l'implémentation de IntList.
Notez private class InnerIterator. De l'extérieur, les appelants voient l'interface publique Iterator<Integer> ; ils ne savent pas que InnerIterator existe. C'est l'avantage d'encapsulation de l'imbrication.
Les classes internes conservent une référence — et maintiennent l'objet externe en vie
Un écueil subtil. Tant qu'une instance d'une classe interne est accessible, la JVM ne peut pas collecter les ordures de l'instance externe à laquelle elle est liée. Retourner une instance de classe interne à du code de longue durée (par exemple, installer un écouteur quelque part) peut maintenir des graphes d'objets entiers en vie plus longtemps que prévu.
public class Window {
Listener installListener() {
return new Listener(); // returned to whoever calls this
}
class Listener { ... } // holds a Window reference forever
}Si installListener() est stocké dans un registre statique, la Window reste en vie jusqu'à ce que le registre soit vidé. La solution consiste généralement à rendre la classe imbriquée static et à passer les données nécessaires explicitement, rompant ainsi la référence implicite.
C'est la raison principale pour laquelle les équipes choisissent par défaut les classes imbriquées static et ne basculent vers les classes internes que lorsqu'elles ont spécifiquement besoin de la liaison.
Membres statiques dans les classes internes
Pendant la majeure partie de l'histoire de Java, les classes internes n'étaient pas autorisées à déclarer des membres static (champs static, méthodes ou classes imbriquées). Java 16 a assoupli cette règle — les classes internes peuvent désormais avoir des membres statiques. Même ainsi, si vous ressentez le besoin d'en avoir, c'est souvent un signal que la classe devrait elle-même être static.
static ou interne — le choix
Une règle utile : rendez-la static sauf si vous avez réellement besoin de la référence externe.
- Classe imbriquée statique : plus simple, plus légère, ne maintient pas l'objet externe en vie.
- Classe interne : sucre syntaxique pratique pour l'accès à
outerInstance.fieldlorsque la relation est authentique.
Si la seule chose que vous faites est Outer.this.field, prenez simplement un Outer comme paramètre de constructeur et rendez la classe statique.
Un exemple complet
Étape suivante
La prochaine variante de classe imbriquée est la version en ligne, à usage unique : les classes anonymes, utilisées pour les situations de sous-classement et d'instanciation rapides en une seule expression. Continuez vers les classes anonymes.