CodeGym /Cours /JAVA 25 SELF /Classes anonymes : différence avec les lambdas, exemples

Classes anonymes : différence avec les lambdas, exemples

JAVA 25 SELF
Niveau 48 , Leçon 4
Disponible

1. Approfondissons les classes anonymes

Une classe anonyme est une sous-classe sans nom ou une implémentation d’interface créée directement à l’endroit de l’utilisation. Avant l’arrivée des lambdas (Java 8), c’était le moyen le plus pratique d’implémenter « à usage unique » une interface ou une classe abstraite.

Classique du genre :

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Bonjour depuis une classe anonyme !");
    }
};
r.run();

Ici, nous avons déclaré puis immédiatement implémenté l’interface Runnable — sans fichier séparé ni nom de classe. Ces implémentations sont souvent utilisées pour des gestionnaires d’événements, des comparateurs, des threads et d’autres tâches où l’on a besoin d’« injecter » rapidement un comportement.

Si une lambda est « une expression à la volée », alors une classe anonyme est « un petit acteur sans nom » : il joue un rôle épisodique et disparaît.

2. Comparaison avec les expressions lambda

Syntaxe

Classe anonyme :

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};

Expression lambda :

Comparator<String> comp = (a, b) -> a.length() - b.length();

La différence est évidente : la lambda est plus compacte — pas besoin d’indiquer explicitement les types, le nom de la méthode ni des accolades superflues si l’action est simple.

Fonctionnalité

  • Classe anonyme — un objet à part entière. On peut déclarer des champs, des méthodes supplémentaires, redéfinir les méthodes de Object (toString, equals, etc.).
  • Expression lambda — implémentation d’une seule méthode abstraite d’une interface fonctionnelle. À l’intérieur, on ne peut pas déclarer de champs propres ni de méthodes supplémentaires.

Quand choisir quoi ?

  • Lambda — lorsque vous devez implémenter brièvement une seule méthode d’une interface fonctionnelle.
  • Classe anonyme — lorsque vous devez :
    • implémenter plusieurs méthodes (par exemple, d’une classe abstraite) ;
    • déclarer des champs pour l’état ;
    • redéfinir les méthodes de Object (par exemple, toString) ;
    • utiliser des particularités d’héritage/d’accès (par exemple, vers des membres protégés de la super-classe).

3. Portée et mot-clé this

Voici un piège fréquent :

  • dans une classe anonyme, this fait référence à l’instance de la classe anonyme ;
  • dans une expression lambda, this fait référence à la classe externe dans laquelle la lambda est déclarée.

Exemple : comparons le comportement

public class Outer {
    String name = "Classe externe";

    void test() {
        Runnable anon = new Runnable() {
            String name = "Classe anonyme";
            @Override
            public void run() {
                System.out.println(this.name); // "Classe anonyme"
            }
        };
        Runnable lambda = () -> System.out.println(this.name); // "Classe externe"

        anon.run();
        lambda.run();
    }
}

Sortie :

Classe anonyme
Classe externe

Dans une classe anonyme, this désigne la classe anonyme elle-même (on prend son champ name). Dans une lambda, this désigne Outer.

4. Quand utiliser des classes anonymes ?

Si vous devez implémenter plus d’une méthode

Une lambda ne fonctionne qu’avec des interfaces fonctionnelles (exactement une méthode abstraite). Si une interface/une classe abstraite exige d’implémenter plusieurs méthodes — il faut une classe anonyme.

abstract class Animal {
    abstract void say();
    abstract void jump();
}

Animal cat = new Animal() {
    @Override
    void say() {
        System.out.println("Miaou !");
    }
    @Override
    void jump() {
        System.out.println("Bond !");
    }
};

Si vous devez conserver un état (champs)

Runnable r = new Runnable() {
    int counter = 0;
    @Override
    public void run() {
        counter++;
        System.out.println("Appelé " + counter + " fois");
    }
};
r.run(); // Appelé 1 fois
r.run(); // Appelé 2 fois

Si vous devez redéfinir les méthodes de Object

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
    @Override
    public String toString() {
        return "Comparateur par longueur de chaîne";
    }
};
System.out.println(comp); // Comparateur par longueur de chaîne

5. Exemples : Comparator et Runnable — lambda vs classe anonyme

Tri des chaînes par longueur

Classe anonyme :

List<String> words = Arrays.asList("chat", "éléphant", "souris", "tigre");
words.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});
System.out.println(words);

Expression lambda :

List<String> words = Arrays.asList("chat", "éléphant", "souris", "tigre");
words.sort((a, b) -> a.length() - b.length());
System.out.println(words);

Le résultat est le même, mais le code avec lambda est plus court et plus lisible.

Runnable : démarrer un thread

Classe anonyme :

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread via une classe anonyme");
    }
});
t1.start();

Expression lambda :

Thread t2 = new Thread(() -> System.out.println("Thread via une lambda"));
t2.start();

Classe anonyme avec champs

Runnable r = new Runnable() {
    int count = 0;
    @Override
    public void run() {
        count++;
        System.out.println("Appelé " + count + " fois");
    }
};
r.run(); // Appelé 1 fois
r.run(); // Appelé 2 fois

Impossible avec une lambda — pas de possibilité de déclarer un champ.

6. Particularités : portée, variables et final

Dans les classes anonymes comme dans les expressions lambda, les variables locales de la méthode externe ne peuvent être utilisées que si elles sont final ou « effectivement final » (elles ne changent pas après l’initialisation). Mais il y a une nuance concernant les noms :

  • dans une classe anonyme, on peut déclarer une variable portant le même nom que dans la portée externe (« masquage ») ;
  • dans une lambda — impossible : le nom ne doit pas entrer en conflit avec celui d’une variable externe.

Exemple :

int x = 10;
Runnable r = new Runnable() {
    @Override
    public void run() {
        int x = 20; // OK : masque la variable externe
        System.out.println(x); // 20
    }
};
r.run();

Runnable l = () -> {
    // int x = 30; // Erreur de compilation : la variable est déjà définie
    System.out.println(x); // 10
};
l.run();

7. Quand la lambda est-elle meilleure, et quand la classe anonyme est-elle indispensable ?

Les expressions lambda — votre choix si :

  • vous devez implémenter une fonction courte pour une interface fonctionnelle ;
  • vous n’avez pas besoin de conserver d’état ;
  • vous n’avez pas besoin de redéfinir les méthodes de Object ;
  • l’implémentation est « ici et maintenant » et reste simple.

La classe anonyme — nécessaire si :

  • il faut implémenter une interface avec plusieurs méthodes ou une classe abstraite ;
  • vous devez déclarer des champs ou des méthodes supplémentaires ;
  • il faut redéfinir toString, equals, hashCode ;
  • il faut un accès aux membres protégés de la super-classe.

8. Pratique : comparaison sur des exemples

Tâche 1 : Filtrer une liste via Predicate

Classe anonyme :

List<String> animals = Arrays.asList("chat", "éléphant", "souris", "tigre");
animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() < 4;
    }
});
System.out.println(animals); // [éléphant, souris, tigre]

Expression lambda :

List<String> animals = Arrays.asList("chat", "éléphant", "souris", "tigre");
animals.removeIf(s -> s.length() < 4);
System.out.println(animals); // [éléphant, souris, tigre]

Tâche 2 : Comparaison de la portée de this

public class Demo {
    String name = "Demo";

    void check() {
        Runnable anon = new Runnable() {
            String name = "Anon";
            @Override
            public void run() {
                System.out.println(this.name); // "Anon"
            }
        };

        Runnable lambda = () -> System.out.println(this.name); // "Demo"

        anon.run();
        lambda.run();
    }

    public static void main(String[] args) {
        new Demo().check();
    }
}

9. Erreurs typiques avec les classes anonymes et les expressions lambda

Erreur n° 1 : s’attendre à ce qu’une lambda puisse implémenter plusieurs méthodes. Une lambda ne fonctionne qu’avec des interfaces fonctionnelles (une seule méthode abstraite). S’il y a plus de méthodes — utilisez une classe anonyme.

Erreur n° 2 : confusion autour de la portée de this. Dans une lambda, this désigne la classe externe ; dans une classe anonyme — la classe anonyme elle-même. Cela peut conduire à des champs et des valeurs « inattendus ».

Erreur n° 3 : tenter de déclarer des champs dans une lambda. Dans une lambda, on ne peut pas déclarer ses propres champs — on ne peut utiliser que les variables du contexte externe (final/« effectivement final »). Pour conserver un état, utilisez une classe anonyme.

Erreur n° 4 : masquage de variables. Dans une classe anonyme, on peut déclarer une variable locale portant le même nom qu’une variable externe — c’est du masquage. Dans une lambda, c’est impossible : le compilateur lèvera une erreur.

Erreur n° 5 : logique trop complexe dans une lambda. Si le corps de la lambda dépasse 35 lignes, la lisibilité en pâtit. Il vaut mieux extraire le code dans une méthode séparée ou utiliser une classe anonyme (si vous avez besoin d’un état/de plusieurs méthodes).

1
Étude/Quiz
Expressions lambda, niveau 48, leçon 4
Indisponible
Expressions lambda
Expressions lambda
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION