Menu

Expressions lambda en Java : du code fonctionnel concis

Ce qu'est une expression lambda en Java, la syntaxe de la flèche, comment elle implémente une interface fonctionnelle, les références de méthode et la capture de variables.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Du comportement que vous pouvez faire circuler

Une expression lambda est un bloc de code compact que vous pouvez passer à une méthode, stocker dans une variable ou renvoyer, exactement comme vous le feriez avec un nombre ou une chaîne. Avant les lambdas, faire circuler du comportement imposait d'écrire une classe entière (ou une classe anonyme verbeuse) juste pour envelopper une méthode. Une lambda ramène cela à l'essentiel : les paramètres et le corps.

Une lambda implémente toujours une interface fonctionnelle, c'est-à-dire une interface dotée d'exactement une méthode abstraite (vous les avez rencontrées à la fin de la page sur les interfaces). Le compilateur déduit du contexte de quelle interface il s'agit et associe votre lambda à cette unique méthode.

x -> x * 2 constitue l'implémentation complète de apply. Pas de new, pas de corps de classe, pas de nom de méthode : l'interface fournit tout cela.

La syntaxe de la flèche

Toute lambda a la forme paramètres -> corps. Les éléments s'adaptent selon vos besoins :

() -> 42                       // aucun paramètre
x -> x + 1                     // un paramètre, parenthèses facultatives
(x, y) -> x + y                // deux paramètres ou plus exigent des parenthèses
(int x, int y) -> x + y        // les types sont facultatifs - généralement inférés
x -> {                          // un corps en bloc nécessite des accolades et return
    int doubled = x * 2;
    return doubled + 1;
}

Un corps composé d'une seule expression (x -> x + 1) renvoie implicitement sa valeur, sans le mot-clé return. Dès l'instant où vous utilisez des accolades, vous écrivez un bloc ordinaire et devez utiliser return explicitement si la méthode renvoie quelque chose. Une erreur fréquente consiste à mélanger les deux : x -> { x + 1 } ne compile pas, car un bloc requiert une instruction (return x + 1;).

Les lambdas remplacent les classes anonymes

La façon la plus claire de voir ce qu'une lambda apporte, c'est l'avant/après. Trier avec un Comparator personnalisé ressemblait autrefois à ceci :

// avant - classe anonyme
names.sort(new Comparator<String>() {
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

La même chose sous forme de lambda tient en une seule ligne lisible :

Comparator est une interface fonctionnelle (son unique méthode abstraite est compare), donc la lambda s'y insère directement. Tout le cérémonial (le new, la classe, la signature de méthode) disparaît, ne laissant que la logique de comparaison.

La boîte à outils java.util.function

Vous avez rarement besoin de déclarer votre propre interface fonctionnelle. Le paquet java.util.function fournit les formes courantes, et presque toutes les API de la bibliothèque Java les acceptent :

  • Function<T, R> - prend un T, renvoie un R (apply)
  • Predicate<T> - prend un T, renvoie un boolean (test)
  • Supplier<T> - ne prend rien, renvoie un T (get)
  • Consumer<T> - prend un T, ne renvoie rien (accept)

Ce sont des interfaces génériques : Function<String, Integer> réutilise les génériques que vous avez vus à la page précédente pour rester typé en toute sûreté. Choisissez celle dont la forme correspond à ce que votre code doit recevoir et produire.

Les références de méthode

Lorsqu'une lambda ne fait qu'appeler une méthode existante, vous pouvez la remplacer par une référence de méthode à l'aide de ::. C'est la même valeur, écrite plus directement :

Les références de méthode existent sous plusieurs formes : String::toUpperCase (une méthode d'instance appelée sur chaque argument), Math::max (une méthode statique), System.out::println (une méthode sur un objet précis) et ArrayList::new (un constructeur). N'y recourez que lorsqu'elles se lisent clairement : si vous devez réfléchir à la forme qui s'applique, une simple lambda fera l'affaire.

La capture de variables

Une lambda peut utiliser des variables locales de la méthode qui l'entoure, mais seulement si elles sont final ou effectivement final : affectées une fois et jamais modifiées. Java capture la valeur au moment où la lambda est créée, donc une variable susceptible de changer ensuite serait ambiguë.

Si vous réaffectez factor où que ce soit, le compilateur rejette la lambda avec « variable used in lambda expression should be final or effectively final ». Lorsque vous avez réellement besoin d'un état mutable partagé, capturez plutôt un objet (un champ, un élément de tableau ou un AtomicInteger), car la référence reste fixe même si son contenu change. Notez que les lambdas, contrairement aux classes anonymes, ne créent pas leur propre portée : this à l'intérieur d'une lambda désigne l'instance englobante, et non la lambda elle-même.

Suite : les Streams

Les lambdas sont la brique de base, pas la destination. Leur véritable intérêt apparaît avec l'API Streams, où vous enchaînez des opérations comme filter, map et reduce (chacune prenant une lambda) pour exprimer des transformations de données sous forme de pipeline lisible plutôt qu'un enchevêtrement de boucles. C'est l'objet de la page suivante.

Questions fréquentes

Qu'est-ce qu'une expression lambda en Java ?

Une lambda est une manière concise d'écrire une instance d'une interface fonctionnelle, c'est-à-dire une interface dotée d'une seule méthode abstraite. Au lieu d'une classe anonyme entière, vous écrivez paramètres -> corps. Le compilateur associe la lambda à l'unique méthode de l'interface, si bien que Runnable r = () -> System.out.println("hi"); constitue un Runnable complet. Les lambdas permettent de faire circuler du comportement comme on ferait circuler des données.

Quelle est la différence entre une lambda et une référence de méthode en Java ?

Les deux produisent une instance d'une interface fonctionnelle. Une lambda possède des paramètres explicites et un corps (s -> s.toUpperCase()), tandis qu'une référence de méthode est l'abréviation d'une lambda qui ne fait qu'appeler une méthode existante (String::toUpperCase). Utilisez une référence de méthode lorsque la lambda se contenterait de transmettre ses arguments à une seule méthode nommée : c'est plus court et plus lisible.

Pourquoi les variables utilisées dans une lambda Java doivent-elles être final ou effectivement final ?

Une lambda peut capturer des variables locales de la méthode qui l'entoure, mais seulement si elles ne changent jamais après leur affectation : c'est ce que signifie « effectivement final ». Java capture la valeur, et non une référence vivante vers la variable, donc autoriser la réaffectation serait ambigu et dangereux entre threads. Si vous avez besoin d'un état mutable partagé, utilisez plutôt un champ ou un conteneur comme un tableau ou un AtomicInteger.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER