W3docs

Les classes anonymes internes en Java

Créez des implémentations ponctuelles d'interfaces et de classes abstraites en Java avec les classes anonymes internes.

Une classe anonyme est une sous-classe ou une implémentation d'interface jetable que vous définissez et instanciez en une seule expression — sans nom, sans fichier séparé, sans en-tête de classe. C'était la façon dont Java gérait les callbacks, les écouteurs et les petits adaptateurs avant l'arrivée des expressions lambda en Java 8. Les lambdas ont remplacé la plupart de leurs cas d'utilisation, mais les classes anonymes restent légales, utiles dans quelques situations spécifiques, et présentes dans les bases de code plus anciennes.

La syntaxe

La forme est new SomeType() { ... body ... }. Le corps est la définition de la classe ; l'expression environnante crée également une instance :

Runnable r = new Runnable() {
  @Override
  public void run() {
    System.out.println("hi");
  }
};
r.run();

Cette seule instruction (1) définit une nouvelle classe qui implémente Runnable, (2) crée une instance de celle-ci, (3) assigne l'instance à r. La classe n'a pas de nom dans votre code source ; le compilateur lui attribue un nom généré comme Outer$1 dans le fichier .class.

Vous pouvez faire de même avec une classe abstraite :

Shape s = new Shape() {
  @Override double area() { return 42; }
};

…ou même une classe concrète, si vous souhaitez redéfinir l'une de ses méthodes à la volée :

ArrayList<String> chatty = new ArrayList<>() {
  @Override
  public boolean add(String s) {
    System.out.println("added " + s);
    return super.add(s);
  }
};

Quand les utiliser

Le cas classique : un callback dont l'implémentation est minuscule et utilisée en un seul endroit :

button.addClickListener(new ClickListener() {
  @Override
  public void onClick() {
    System.out.println("clicked");
  }
});

En Java moderne, vous écririez généralement ceci sous forme de lambda :

button.addClickListener(() -> System.out.println("clicked"));

…et le lambda est plus court, plus lisible, et ne maintient pas de référence à la classe englobante. Alors quand la forme anonyme a-t-elle encore du sens ?

  • Plusieurs méthodes. Un lambda implémente une seule méthode abstraite. Si vous devez redéfinir deux méthodes sur une classe abstraite ou en implémenter deux sur une interface, seule une classe anonyme fera l'affaire.
  • Sous-classer une classe concrète. Les lambdas ne ciblent que les interfaces fonctionnelles. Pour redéfinir une méthode sur ArrayList, HashMap, ou votre propre classe concrète à la volée, vous avez besoin d'une classe anonyme.
  • Appeler super.method(...) dans la redéfinition. Les lambdas n'ont pas de super. Les classes anonymes si.
  • Blocs d'initialisation. Les classes anonymes peuvent avoir des blocs initialiseurs d'instance ({ ... }) ; les lambdas non.

En pratique, ce sont de rares cas. La plupart des callbacks modernes utilisent des lambdas.

Capture de variables

Une classe anonyme déclarée à l'intérieur d'une méthode peut lire les variables locales de la méthode englobante — mais uniquement si elles sont final ou effectivement finales (jamais réassignées après leur initialisation) :

void schedule() {
  String msg = "hello";
  Runnable r = new Runnable() {
    @Override public void run() {
      System.out.println(msg);     // captures msg
    }
  };
  r.run();
  // msg = "bye";          // would make msg no longer effectively final → ERROR above
}

La même règle s'applique aux lambdas — c'est une propriété du code environnant, pas de la forme syntaxique. La raison est que la valeur capturée est copiée dans les champs de la classe synthétique ; si msg pouvait changer par la suite, la copie capturée deviendrait silencieusement obsolète.

Elles ont un this externe

Comme les classes internes, les classes anonymes déclarées dans un contexte non statique portent une référence à l'instance englobante. Cela signifie que this à l'intérieur de la classe anonyme fait référence à l'instance anonyme, pas à l'instance externe. Pour accéder à l'instance externe, qualifiez-la avec Outer.this :

public class Server {
  String name = "outer";

  Runnable handler() {
    return new Runnable() {
      String name = "inner";
      public void run() {
        System.out.println(name);            // "inner"
        System.out.println(Server.this.name);// "outer"
      }
    };
  }
}

Et même mise en garde concernant la référence externe que pour les classes internes : retourner une instance anonyme à du code de longue durée maintient l'instance externe en vie.

Limitations

  • Une classe anonyme peut étendre une superclasse ou implémenter une interface — pas les deux, et pas plus d'une des deux.
  • Elle ne peut pas avoir de constructeur propre — il n'y a pas de nom à donner au constructeur. Vous pouvez utiliser un bloc initialiseur d'instance ({ ... }) pour la configuration.
  • Elle ne peut pas être static.

Comparaison avec les lambdas — la comparaison en code

Même travail, deux façons :

// Anonymous class — older style
Comparator<String> byLength = new Comparator<String>() {
  @Override
  public int compare(String a, String b) {
    return Integer.compare(a.length(), b.length());
  }
};

// Lambda — modern equivalent
Comparator<String> byLength = (a, b) -> Integer.compare(a.length(), b.length());

Les deux produisent un Comparator<String>. Le lambda est plus court et a des sémantiques de this/portée légèrement différentes (pas de référence à la classe externe pour les contextes d'instance ; this à l'intérieur d'un lambda est l'instance englobante, pas un nouvel objet). Si les deux formes conviennent à votre cas d'utilisation, préférez le lambda.

Un exemple concret

java— editable, runs on the server

La suite

Le dernier type de classe imbriquée restant est la classe locale — une classe déclarée à l'intérieur d'un corps de méthode, avec un vrai nom mais une portée réduite. Elles se recoupent avec les classes anonymes (et avec les lambdas), mais sont parfois le choix le plus clair. Continuez vers les classes locales.

Pratique

Pratique
En Java moderne, quelle tâche nécessite encore une classe anonyme plutôt qu'un lambda ?
En Java moderne, quelle tâche nécessite encore une classe anonyme plutôt qu'un lambda ?
Was this page helpful?