Comment utiliser les lambdas en Java
L'auteur a choisi Free and Open Source Fund pour recevoir un don dans le cadre du programme Write for Donations.
Introduction
Les lambdas, également connus sous le nom de fonctions anonymes et de closures, sont des blocs de code qui peuvent être transmis et exécutés ultérieurement. Les lambdas sont présents en Java et dans la plupart des langages de programmation modernes pour permettre d'écrire un code plus concis avec moins de code standard.
Dans ce tutoriel, vous apprendrez à écrire vos propres expressions lambda. Vous apprendrez également à utiliser certaines lambdas intégrées disponibles dans le package java.util.function. Elles couvrent des cas d'utilisation courants et facilitent le démarrage avec les lambdas.
Prérequis
Pour suivre ce tutoriel, vous aurez besoin de :
-
Un environnement dans lequel vous pouvez exécuter des programmes Java pour suivre les exemples. Pour configurer cela sur votre machine locale, vous aurez besoin de ce qui suit :
- Java (version 11 ou supérieure) installé sur votre machine, avec le compilateur fourni par le Java Development Kit (JDK). Pour Ubuntu et Debian, suivez les étapes de l'Option 1 dans notre tutoriel Comment installer Java avec Apt sur Ubuntu 22.04. Pour les autres systèmes d'exploitation, y compris Mac et Windows, suivez les options de téléchargement pour l'installation de Java.
- Pour compiler et exécuter les exemples de code, ce tutoriel utilise Java Shell, également connu sous le nom de JShell, qui est un Read-Evaluate-Print Loop (REPL) exécuté à partir de la ligne de commande. Pour commencer avec JShell, consultez le guide Introduction à JShell.
Familiarité avec Java et la programmation orientée objet, que vous pouvez trouver dans notre tutoriel Comment écrire votre premier programme en Java.
Une compréhension des types de données Java, qui est discutée dans notre tutoriel Comprendre les types de données en Java.
Une compréhension des listes Java, sur lesquelles de nombreux exemples sont basés. Vous pouvez en savoir plus sur les listes dans Comment utiliser les listes en Java.
Créer votre propre Lambda
Pour créer votre expression lambda, vous devez d'abord comprendre sa structure. Elle peut être décrite comme suit :
(argument(s)) -> {body}Une expression lambda comporte trois parties :
- Une liste de zéro argument ou plus, séparés par des virgules, et entourés de parenthèses. Leur type (
String,Integer, etc.) peut être automatiquement déduit par le compilateur, donc vous n'avez pas à le déclarer. - La flèche
->. Elle est utilisée pour relier les paramètres et le corps de l'expression lambda. - Un corps qui contient le code à exécuter. S'il y a des arguments, ils sont utilisés dans le corps pour effectuer des opérations avec eux.
Info : Pour suivre le code d'exemple dans ce tutoriel, ouvrez l'outil Java Shell sur votre système local en exécutant la commande jshell. Ensuite, vous pouvez copier, coller ou modifier les exemples en les ajoutant après l'invite jshell> et en appuyant sur ENTRÉE. Pour quitter jshell, tapez /exit.
Voici un exemple d'expression lambda :
(x) -> System.out.println(x)Dans ce lambda, x est le paramètre qui est imprimé avec la méthode System.out.println(). Une bonne utilisation de ce lambda est d'imprimer le contenu d'une liste fournie par la méthode foreach. Pour le tester, dans jshell créez une liste d'animaux de compagnie comme ceci :
List<String> pets = Arrays.asList("Dog", "Cat")Vous verrez une confirmation que la liste a été créée :
pets ==> [Dog, Cat]
Vous pouvez maintenant imprimer le contenu de la liste en utilisant la méthode foreach et l'expression lambda :
pets.forEach((x) -> System.out.println(x))Vous verrez chaque animal de compagnie imprimé sur une nouvelle ligne :
Dog
CatLe lambda (x) -> System.out.println(x) peut être simplifié davantage car il n'a qu'un seul paramètre. Dans ce cas, vous pouvez :
- supprimez les parenthèses autour du paramètre
x. - supprimez complètement le paramètre
xet utilisez seulement la référence de méthodeSystem.out::println. Les références de méthode sont une notation abrégée pour une expression lambda permettant d'appeler une méthode. Elles sont considérées comme plus lisibles et recommandées par les bonnes pratiques du code propre.
Ainsi, le même code peut être réécrit comme :
pets.forEach(System.out::println)Cependant, toutes les lambdas ne sont pas si simples et ne peuvent pas être réduites à une seule ligne. Vous devrez probablement utiliser des lambdas avec plus de paramètres et le bloc d'exécution occupera plus d'une seule ligne. C'est à ce moment que la syntaxe devient plus complexe. Par exemple, ajoutons également le nombre d'animaux lors de l'impression :
pets.forEach(x -> {
System.out.println("Pet name: " + x);
System.out.println("Pet number: " + pets.indexOf(x));
});Remarquez comment l'expression lambda est maintenant encadrée par des accolades supplémentaires {} parce qu'elle occupe plus d'une ligne. De plus, pour obtenir l'indice de l'animal dans la liste, vous devez utiliser la méthode indexOf de la liste et lui passer le nom de l'animal comme paramètre pets.indexOf(x).
Le code ci-dessus produira la sortie suivante :
Pet name: Dog
Pet number: 0
Pet name: Cat
Pet number: 1Voici comment vous pouvez écrire vos expressions lambda en Java. Dans la section suivante, vous apprendrez comment utiliser les lambdas intégrées disponibles dans le paquet java.util.function.
Utilisation des lambdas intégrés
Le package java.util.function contient un ensemble d'interfaces fonctionnelles qui couvrent les cas d'utilisation courants. Pour utiliser l'une de ces interfaces, vous devez fournir l'implémentation de sa méthode abstraite. Voyons comment cela peut être fait pour certaines des interfaces les plus utiles.
Prédicats
Un predicate est un lambda servant à tester une condition. Il possède une méthode test qui renvoie un booléen - true si la condition est remplie et false sinon. Par exemple, vous pouvez vérifier si les valeurs d'une liste de chaînes commencent par une lettre spécifique. Utilisons à nouveau la liste des animaux de compagnie et affichons chaque nom d'animal qui commence par la lettre « D » :
Predicate<String> filterPets = x -> x.startsWith("D");
pets.stream().filter(t -> filterPets.test(t)).forEach(System.out::println);
Le code peut être décomposé comme suit :
- Sur la première ligne, vous créez un type de prédicat pour les valeurs de chaîne appelé
filterPets. Le type de chaîne est défini à l'intérieur de l'opérateur diamant<>. - Avec le signe égal, le prédicat est attribué à une expression lambda qui vérifie si l'argument passé
xcommence par la lettre D. - Sur la deuxième ligne, vous commencez par itérer sur la liste des animaux de compagnie en utilisant sa méthode
stream(). - Ensuite, sur la deuxième ligne, suit la méthode
filterqui est utilisée pour filtrer les valeurs en fonction d'un prédicat. La méthodefilterPetsdéfinie précédemment est passée à cette méthode à cet effet. - Enfin, vous utilisez la méthode
forEachpour parcourir les valeurs filtrées et imprimer chacune d'entre elles en utilisant la référence de méthodeSystem.out::println.
La sortie de jshell sera similaire à ce qui suit :
filterPets ==> $Lambda$32/0x0000...
DogLa première ligne peut être ignorée. Elle confirme qu'une référence filterPets a été créée et que son emplacement en mémoire est indiqué. Vous verrez de telles lignes dans jshell lors de la déclaration de lambdas. Vous pouvez les ignorer pour l'instant car elles ne sont utiles que pour le débogage.
La deuxième ligne imprime le nom du seul animal de compagnie qui commence par la lettre D - Chien.
L'exemple explicite ci-dessus est utile pour apprendre et comprendre comment fonctionne un predicate. Cependant, en pratique, il peut être écrit beaucoup plus simplement. Une fois que vous avez défini le predicate, vous pouvez l'utiliser directement sans passer explicitement des arguments dans un lambda. Cela est dû au fait que chaque predicate, tout comme chaque lambda, n'a qu'une seule méthode. Java peut automatiquement comprendre que cette méthode doit être appelée avec le seul paramètre disponible. Ainsi, le même code peut être réécrit comme suit :
Predicate<String> filterPets = x -> x.startsWith("D");
pets.stream().filter(filterPets).forEach(System.out::println);Définir le prédicat séparément et l'assigner à un objet tel que filterPets a du sens si vous prévoyez de l'utiliser plus d'une fois dans votre code. Cependant, si vous allez filtrer les animaux de compagnie par leur première lettre seulement une fois, il sera plus logique d'écrire le prédicat directement dans la méthode filter comme ceci :
pets.stream().filter(x -> x.startsWith("D")).forEach(System.out::println);Dans tous les cas, le résultat sera le même. La différence réside dans le degré d'explicitation et de réutilisabilité que vous souhaitez pour votre code. Cela sera valable pour le reste des exemples de ce tutoriel.
Consommateurs
Un consumer est utilisé pour consommer une valeur. Il possède une méthode accept qui retourne void, c’est-à-dire ne retourne rien. Sans vous en rendre compte, lorsque vous avez créé votre première lambda pour imprimer la liste des animaux de compagnie, vous avez implémenté un consumer.
Définissons un consommateur plus explicitement de cette façon :
Consumer<String> printPet = x -> System.out.println(x);
pets.forEach(printPet);Comme prévu, la sortie listera les deux animaux de compagnie comme ceci :
Dog
CatTout comme dans le lambda prédicat, ce qui précède est la manière la plus explicite de définir et d'utiliser un consommateur afin que vous puissiez le comprendre au mieux.
Fonctions
Une fonction est utilisée pour transformer une valeur qui est passée comme argument. Elle possède une méthode apply qui retourne la valeur transformée. Par exemple, vous pouvez utiliser une fonction pour transformer chacun des animaux de compagnie et le mettre en majuscules :
Function<String, String> toUpperCase = x -> x.toUpperCase();
pets.stream().map(x -> toUpperCase.apply(x)).forEach(System.out::println);Lorsque vous définissez une fonction, vous devez spécifier les types d'entrée et de sortie. Dans notre exemple, les deux sont des chaînes. C'est pourquoi la première ligne commence par Function<String, String> pour déclarer la référence toUpperCase. Après le signe égal suit l'expression lambda dans laquelle la méthode de chaîne toUpperCase joue le rôle clé en transformant la chaîne d'entrée en majuscules.
Sur la deuxième ligne, la liste des animaux de compagnie est convertie en flux et la méthode map est invoquée. Cette méthode est utilisée pour transformer chaque animal en majuscules avec la lambda toUpperCase. Enfin, vous utilisez la méthode forEach pour imprimer chaque animal. La sortie de jshell sera :
DOG
CATFournisseurs
Un supplier est utilisé pour fournir une valeur à l'appelant et n'accepte pas d'arguments. Il possède une méthode get qui renvoie la valeur souhaitée. Voici un exemple de supplier qui donne l'heure actuelle :
Supplier<java.time.LocalTime> timeSupplier = () -> java.time.LocalTime.now();
System.out.println(timeSupplier.get());Sur la première ligne, vous définissez un fournisseur pour les types <java.time.LocalTime> appelé timeSupplier. Il est assigné à une lambda sans paramètres dont le code d'exécution retourne l'heure actuelle telle que fournie par la méthode intégrée java.time.LocalTime.now. Enfin, vous utilisez la méthode get pour obtenir la valeur de la lambda timeSupplier et l'imprimer avec System.out.println.
Lorsque vous exécutez le code ci-dessus dans jshell, vous verrez l'heure actuelle imprimée comme ceci :
21:52:38.384278Opérateurs unaires
De manière similaire à une function, un unaryOperator est également utilisé pour transformer une valeur qui est passée en argument. Cependant, avec un unaryOperator, les types d'entrée et de sortie sont toujours les mêmes. Sa méthode s'appelle également apply et renvoie la valeur transformée. Voici comment vous pouvez utiliser un unaryOperator pour transformer une chaîne comme « dog » en majuscules :
UnaryOperator<String> toUpperCase = x -> x.toUpperCase();
System.out.println(toUpperCase.apply("dog"));Dans l'exemple ci-dessus, vous définissez un unaryOperator appelé toUpperCase qui prend une chaîne de caractères et retourne une autre chaîne en majuscules. Ensuite, vous utilisez la méthode apply pour transformer la chaîne « dog » en « DOG » et l'afficher avec System.out.println. Lorsque vous exécutez le code ci-dessus dans jshell, vous verrez la chaîne « DOG » imprimée comme ceci :
DOGOpérateurs binaires
Un binaryOperator est utilisé pour combiner deux valeurs. Il possède également une méthode apply qui renvoie la valeur combinée. Par exemple, vous pouvez créer une calculatrice qui additionne deux nombres comme ceci :
BinaryOperator<Integer> addition = (x, y) -> x + y;
System.out.println(add.apply(2, 3));La première ligne définit un binaryOperator appelé addition qui prend des arguments entiers et retourne leur somme. Ensuite, vous utilisez la méthode apply pour additionner les nombres 2 et 3 et afficher le résultat avec System.out.println. Lorsque vous exécutez le code ci-dessus dans jshell, vous verrez :
add ==> $Lambda$29/0x000...
5Voici comment vous pouvez utiliser les interfaces lambda intégrées disponibles dans le package java.util.function pour créer vos implémentations utiles et créer des extraits de code utiles.
Conclusion
Dans ce tutoriel, vous avez appris comment écrire vos expressions lambda en Java. Vous avez également appris à utiliser les lambdas intégrés disponibles dans le package java.util.function qui couvre des scénarios courants. Maintenant, vous pouvez utiliser ces lambdas pour rendre votre code plus propre et lisible.
