1. Avantages des expressions lambda
Les expressions lambda ne sont pas qu’un simple sucre syntaxique, mais une avancée vers le style fonctionnel en Java. Ci‑dessous — leurs véritables atouts et pourquoi il est si pratique de les utiliser dans du code moderne.
Concision et expressivité
Avant l’arrivée des lambdas, un code « local » simple se transformait en classe anonyme avec beaucoup de bruit « boilerplate ». Par exemple, trier des chaînes par longueur :
Avant Java 8 (classe anonyme) :
list.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Avec une expression lambda :
list.sort((a, b) -> a.length() - b.length());
Le code est plus court et se lit presque comme du langage naturel : « trier par différence de longueur ».
Lisibilité et focus sur l’essentiel
Les lambdas suppriment le « bruit cérémoniel » — noms de classes, accolades superflues, return, qui n’ajoutent pas de sens. Résultat : le code est plus simple à lire et à maintenir :
names.forEach(name -> System.out.println(name));
Tout est évident : pour chaque nom — l’imprimer. Ici, il est utile de connaître des méthodes de collection comme forEach.
Passer un comportement en paramètre
Il est enfin devenu pratique de passer un « bout de comportement » en paramètre de méthode. Cela se ressent particulièrement avec les collections, l’API Stream, et les événements :
Exemple : filtrer une liste de nombres
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.removeIf(n -> n % 2 == 0); // On supprime les nombres pairs
Excellente intégration avec les collections et l’API Stream
List<String> words = Arrays.asList("Java", "Python", "C++");
List<String> upper = words.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
Capture de variables (closures)
Les lambdas peuvent « capturer » des variables depuis le contexte externe (si elles sont effectivement final). Cela permet de créer des fonctions à la volée qui se souviennent de leur environnement :
int minLength = 3;
list.removeIf(s -> s.length() < minLength);
La variable minLength est déclarée à l’extérieur, mais accessible à l’intérieur de la lambda.
Écriture naturelle pour les événements et les callbacks
button.addActionListener(e -> System.out.println("Bouton cliqué !"));
Plus besoin d’une classe séparée ou anonyme pour une seule ligne.
Simplification des tests
On peut rapidement injecter des « stubs » sans multiplier les classes :
doSomething(() -> System.out.println("Gestionnaire de test"));
2. Inconvénients et limites des expressions lambda
Comme tout outil, elles ont aussi leurs écueils.
Difficultés de débogage
Les lambdas sont des fonctions anonymes ; en cas d’erreur, la pile d’appels peut manquer de clarté. Les points d’arrêt fonctionnent, mais avec des lambdas longues/imbriquées, il peut être difficile de voir où se situe exactement le problème.
list.stream()
.filter(s -> s.length() > 3)
.map(s -> s.toUpperCase())
.forEach(System.out::println);
Parfois, « déplier » la chaîne en variables intermédiaires aide.
Ambiguïté de l’interface ciblée
En présence de surcharges acceptant différentes interfaces fonctionnelles, le compilateur peut ne pas deviner quelle interface une lambda implémente (par exemple, Runnable avec void, ou Callable avec une valeur String).
void doSomething(Runnable r) { /* ... */ }
void doSomething(Callable<String> c) { /* ... */ }
// doSomething(() -> "Hello"); // Ambiguïté !
Peu adaptées à une logique complexe
Si le corps d’une lambda atteint 3–5 lignes (beaucoup de conditions/boucles), le code perd en lisibilité — mieux vaut extraire la logique dans une méthode nommée.
Mauvais :
list.removeIf(s -> s.length() > 3 && s.contains("Java") && s.startsWith("A") && ...);
Mieux :
list.removeIf(this::isComplexCondition);
private boolean isComplexCondition(String s) {
return s.length() > 3 && s.contains("Java") && s.startsWith("A") && ...;
}
Contraintes de sérialisation
Les lambdas ne sont pas toujours sérialisables. Si vous devez transmettre de la logique entre des JVM (systèmes distribués), il est plus sûr d’utiliser des classes anonymes ou nommées, ou des interfaces prenant explicitement en charge Serializable.
Contraintes de portée
Dans une lambda, on ne peut pas modifier les variables de la méthode externe si elles ne sont pas final ou « effectivement » final.
int count = 0;
list.forEach(s -> count++); // Le compilateur ne l'autorisera pas !
Peu adaptées à la réutilisation
Les lambdas sont des fonctions « jetables ». Si la logique doit être utilisée à plusieurs endroits — extrayez‑la dans une méthode ou une classe avec un nom explicite.
Difficultés avec les lambdas imbriquées
Une forte imbrication (surtout dans les flux/gestionnaires d’événements) transforme rapidement le code en « spaghetti ». Mieux vaut éviter l’imbrication ou découper en étapes.
Quand utiliser les expressions lambda
- Opérations courtes et simples : filtrage, tri, transformation de collections, gestion d’événements.
- Si une lambda dépasse 3–5 lignes — extrayez‑la dans une méthode séparée.
- N’utilisez pas de lambdas pour une logique métier complexe — donnez un nom et des commentaires à la logique.
- N’abusez pas des lambdas imbriquées.
- Extrayez une lambda répétée dans une méthode (ou une méthode statique) et utilisez une référence du type this::method ou ClassName::method.
- Donnez des noms significatifs aux paramètres à l’intérieur de la lambda — cela améliore la lisibilité.
3. Recommandations pratiques
Découpez les chaînes complexes en étapes
Au lieu d’une seule longue chaîne — utilisez des variables intermédiaires :
Stream<String> filtered = list.stream().filter(s -> s.length() > 3);
Stream<String> upper = filtered.map(String::toUpperCase);
upper.forEach(System.out::println);
Utilisez des méthodes nommées pour les conditions complexes
Plutôt qu’une longue lambda :
list.removeIf(s -> s.length() > 3 && s.contains("Java"));
Mieux :
list.removeIf(this::isJavaString);
private boolean isJavaString(String s) {
return s.length() > 3 && s.contains("Java");
}
N’ayez pas peur de commenter
Si une lambda n’est pas évidente — ajoutez un commentaire avant :
// On supprime toutes les chaînes qui commencent par un espace
list.removeIf(s -> s.startsWith(" "));
4. Erreurs typiques lors de l’utilisation des expressions lambda
Erreur n°1 : Lambda trop complexe. Les débutants essaient de faire tenir toute la logique métier dans une seule lambda. On obtient des « monstres » de 10 lignes, difficiles à lire et à maintenir. N’ayez pas peur d’extraire du code dans des méthodes !
Erreur n°2 : Portée peu évidente. Certains essaient de modifier des variables de la méthode externe à l’intérieur d’une lambda — le compilateur proteste. Rappelez‑vous : les variables doivent être final ou « effectivement » final.
Erreur n°3 : Surcharge de méthodes. S’il existe deux surcharges acceptant différentes interfaces fonctionnelles, le compilateur peut ne pas comprendre laquelle vous voulez appeler. Dans ces cas, indiquez le type explicitement :
doSomething((Runnable) () -> System.out.println("Hello"));
Erreur n°4 : Abus de lambdas imbriquées. Des lambdas imbriquées transforment le code en « spaghetti » illisible. Faites une pause et extrayez une partie du code dans une méthode séparée.
Erreur n°5 : Utiliser une lambda là où un objet complet est nécessaire. Si vous devez redéfinir plusieurs méthodes, ajouter des champs ou un comportement non standard — utilisez une classe anonyme ou nommée, et non une lambda.
GO TO FULL VERSION