Annotation de traduction : mapper (cartographie) et réduire (réduire, simplifier) sont deux concepts très fondamentaux en mathématiques. Ils sont apparus dans divers langages de programmation fonctionnels depuis longtemps. Ce n'est qu'en 2003 que Google les a repris et. les a appliqués à Après la mise en œuvre du calcul parallèle dans les systèmes distribués, le nom de cette combinaison a commencé à briller dans le monde informatique (ces fans fonctionnels ne le pensent peut-être pas). Dans cet article, nous verrons les débuts des combinaisons map et reduction après que Java 8 ait pris en charge la programmation fonctionnelle (il ne s'agit que d'une introduction préliminaire, il y aura des sujets spéciaux à leur sujet plus tard).
Réduire un ensemble
Jusqu'à présent, nous avons introduit plusieurs nouvelles techniques pour gérer les collections : recherche d'éléments correspondants, recherche d'éléments individuels et transformations de collections. Ces opérations ont un point commun : elles opèrent toutes sur un seul élément de la collection. Il n'est pas nécessaire de comparer des éléments ou d'effectuer des opérations sur deux éléments. Dans cette section, nous voyons comment comparer des éléments et maintenir dynamiquement un résultat d'opération pendant le parcours de la collection.
Commençons par des exemples simples, puis progressons progressivement. Dans le premier exemple, nous parcourons d’abord la collection d’amis et calculons le nombre total de caractères dans tous les noms.
Copiez le code comme suit :
System.out.println("Nombre total de caractères dans tous les noms : " + friends.stream()
.mapToInt(nom -> nom.longueur())
.somme());
Pour calculer le nombre total de caractères, nous devons connaître la longueur de chaque nom. Cela peut être facilement réalisé grâce à la méthode mapToInt(). Après avoir converti les noms en longueurs correspondantes, il suffit de les additionner à la fin. Nous avons une méthode sum() intégrée pour accomplir cela. Voici le résultat final :
Copiez le code comme suit :
Nombre total de caractères dans tous les noms : 26
Nous avons utilisé une variante de l'opération map, la méthode mapToInt() (telle que mapToInt, mapToDouble, etc., qui générera des types spécifiques de flux, tels que IntStream, DoubleStream), puis avons calculé le nombre total de caractères en fonction du longueur retournée.
En plus d'utiliser la méthode sum, il existe de nombreuses méthodes similaires qui peuvent être utilisées, telles que max() pour trouver la longueur maximale, min() pour trouver la longueur minimale, sorted() pour trier les longueurs, moyenne() pour trouver la longueur moyenne, etc. attendez.
Un autre aspect intéressant de l'exemple ci-dessus est le mode MapReduce de plus en plus populaire. La méthode map() effectue le mappage, et la méthode sum() est une opération de réduction couramment utilisée. En fait, l'implémentation de la méthode sum() dans le JDK utilise la méthode réduire(). Jetons un coup d'œil à certaines des formes d'opérations de réduction les plus couramment utilisées.
Par exemple, nous parcourons tous les noms et imprimons celui avec le nom le plus long. S'il y a plusieurs noms les plus longs, nous imprimons celui trouvé en premier. Une solution consiste à calculer la longueur maximale, puis à sélectionner le premier élément qui correspond à cette longueur. Cependant, cela nécessite de parcourir la liste deux fois, ce qui est trop inefficace. C’est là que l’opération de réduction entre en jeu.
Nous pouvons utiliser l'opération de réduction pour comparer les longueurs de deux éléments, puis renvoyer le plus long et le comparer davantage avec les éléments restants. Comme d’autres fonctions d’ordre supérieur que nous avons vues précédemment, la méthode réduire() parcourt également l’ensemble de la collection. Entre autres choses, il enregistre le résultat du calcul renvoyé par l'expression lambda. S'il existe un exemple qui peut nous aider à mieux comprendre cela, examinons d'abord un morceau de code.
Copiez le code comme suit :
final Facultatif<String> aLongName = friends.stream()
.reduce((nom1, nom2) ->
nom1.length() >= nom2.length() ? nom1 : nom2);
aLongName.ifPresent(nom ->
System.out.println(String.format("Un nom le plus long : %s", nom)));
L'expression lambda passée à la méthode réduire() reçoit deux paramètres, nom1 et nom2, compare leurs longueurs et renvoie la plus longue. La méthode réduire() n’a aucune idée de ce que nous allons faire. Cette logique est supprimée dans l'expression lambda que nous transmettons - il s'agit d'une implémentation légère du modèle Strategy.
Cette expression lambda peut être adaptée à la méthode apply de l'interface fonctionnelle d'un BinaryOperator dans le JDK. C’est exactement le type d’argument accepté par la méthode de réduction. Exécutons cette méthode de réduction et voyons si elle peut sélectionner correctement le premier des deux noms les plus longs.
Copiez le code comme suit :
Un nom le plus long : Brian
Lorsque la méthode réduire() parcourt la collection, elle appelle d'abord l'expression lambda sur les deux premiers éléments de la collection, et le résultat renvoyé par l'appel continue d'être utilisé pour l'appel suivant. Lors du deuxième appel, la valeur de name1 est liée au résultat de l'appel précédent et la valeur de name2 est le troisième élément de la collection. Les éléments restants sont également appelés dans cet ordre. Le résultat du dernier appel d'expression lambda est le résultat renvoyé par l'ensemble de la méthode réduire().
La méthode réduire() renvoie une valeur facultative car la collection qui lui est transmise peut être vide. Dans ce cas, il n’y aurait pas de nom plus long. Si la liste ne contient qu'un seul élément, la méthode réduire renvoie directement cet élément et n'appelle pas l'expression lambda.
De cet exemple, nous pouvons déduire que le résultat de la réduction ne peut être qu'un seul élément de l'ensemble. Si nous souhaitons renvoyer une valeur par défaut ou de base, nous pouvons utiliser une variante de la méthode réduire() qui accepte un paramètre supplémentaire. Par exemple, si le nom le plus court est Steve, nous pouvons le transmettre à la méthode réduire() comme ceci :
Copiez le code comme suit :
chaîne finale steveOrLonger = amis.stream()
.reduce("Steve", (nom1, nom2) ->
nom1.length() >= nom2.length() ? nom1 : nom2);
S'il y a un nom plus long que lui, alors ce nom sera sélectionné ; sinon, la valeur de base Steve sera renvoyée. Cette version de la méthode réduire() ne renvoie pas d'objet facultatif, car si la collection est vide, une valeur par défaut sera renvoyée quel que soit le cas où il n'y a pas de valeur de retour ;
Avant de terminer ce chapitre, examinons une opération très basique mais pas si simple dans les opérations sur les ensembles : la fusion d'éléments.
Fusionner des éléments
Nous avons appris à rechercher des éléments, à parcourir et à convertir des collections. Cependant, il existe une autre opération courante - l'épissage des éléments de la collection - sans cette fonction join() nouvellement ajoutée, le code concis et élégant mentionné précédemment serait vain. Cette méthode simple est si pratique qu’elle est devenue l’une des fonctions les plus couramment utilisées dans le JDK. Voyons comment l'utiliser pour imprimer des éléments d'une liste, séparés par des virgules.
Nous utilisons toujours cette liste d'amis. Si vous utilisez l'ancienne méthode de la bibliothèque JDK, que devez-vous faire si vous souhaitez imprimer tous les noms séparés par des virgules ?
Nous devons parcourir la liste et imprimer les éléments un par un. La boucle for de Java 5 est améliorée par rapport à la précédente, alors utilisons-la.
Copiez le code comme suit :
pour(Nom de la chaîne : amis) {
System.out.print(nom + ", ");
}
System.out.println();
Le code est très simple, voyons quel est son résultat.
Copiez le code comme suit :
Brian, Nate, Neal, Raju, Sara, Scott,
Bon sang, il y a cette virgule agaçante à la fin (pouvons-nous blâmer Scott à la fin ?). Comment puis-je dire à Java de ne pas mettre de virgule ici ? Malheureusement, la boucle s'exécute étape par étape et il n'est pas facile de faire quelque chose de spécial à la fin. Afin de résoudre ce problème, nous pouvons utiliser la méthode de boucle originale.
Copiez le code comme suit :
for(int i = 0; i < amis.size() - 1; i++) {
System.out.print(friends.get(i) + ", ");
}
si (amis.size() > 0)
System.out.println(friends.get(friends.size() - 1));
Voyons si la sortie de cette version est OK.
Copiez le code comme suit :
Brian, Nate, Neal, Raju, Sara, Scott
Le résultat est quand même bon, mais ce code n'est pas flatteur. Sauve-nous, Java.
Nous n’avons plus à supporter cette douleur. La classe StringJoiner dans Java 8 nous aide à résoudre ces problèmes, mais la classe String ajoute également une méthode de jointure afin que nous puissions remplacer les éléments ci-dessus par une seule ligne de code.
Copiez le code comme suit :
System.out.println(String.join(", ", amis));
Venez jeter un œil, les résultats sont aussi satisfaisants que le code.
Copiez le code comme suit :
Brian, Nate, Neal, Raju, Sara, Scott
Le résultat est quand même bon, mais ce code n'est pas flatteur. Sauve-nous, Java.
Nous n’avons plus à supporter cette douleur. La classe StringJoiner dans Java 8 nous aide à résoudre ces problèmes, mais la classe String ajoute également une méthode de jointure afin que nous puissions remplacer les éléments ci-dessus par une seule ligne de code.
Copiez le code comme suit :
System.out.println(String.join(", ", amis));
Venez jeter un œil, les résultats sont aussi satisfaisants que le code.
Copiez le code comme suit :
Brian, Nate, Neal, Raju, Sara, Scott
Dans l'implémentation sous-jacente, la méthode String.join() appelle la classe StringJoiner pour diviser la valeur transmise en tant que deuxième paramètre (qui est un paramètre de longueur variable) en une longue chaîne, en utilisant le premier paramètre comme délimiteur. Bien sûr, cette méthode va bien au-delà du simple collage de virgules. Par exemple, nous pouvons transmettre un tas de chemins et épeler facilement un chemin de classe, grâce à ces méthodes et classes nouvellement ajoutées.
Nous savons déjà comment connecter des éléments de liste. Avant de connecter des listes, nous pouvons également transformer les éléments. Bien sûr, nous savons également utiliser la méthode map pour transformer des listes. Ensuite, nous pouvons également utiliser la méthode filter() pour filtrer les éléments souhaités. La dernière étape de connexion des éléments de la liste, à l'aide de virgules ou d'un autre délimiteur, n'est qu'une simple opération de réduction.
Nous pouvons utiliser la méthode réduire() pour concaténer les éléments dans une chaîne, mais cela nécessite un certain travail de notre part. JDK a une méthode collect() très pratique, qui est également une variante de réduire(). Nous pouvons l'utiliser pour combiner des éléments dans une valeur souhaitée.
La méthode collect() effectue l'opération de réduction, mais elle délègue l'opération spécifique à un collecteur pour exécution. Nous pouvons fusionner les éléments convertis dans une ArrayList. En reprenant l'exemple précédent, nous pouvons concaténer les éléments convertis en une chaîne séparée par des virgules.
Copiez le code comme suit :
System.out.println(
amis.stream()
.map(String::toUpperCase)
.collect(joining(", ")));
Nous avons appelé la méthode collect() sur la liste convertie, en lui transmettant un collecteur renvoyé par la méthode join(). Joining est une méthode statique dans la classe d'outils Collectors. Le collecteur est comme un récepteur. Il reçoit les objets transmis par collect et les stocke dans le format souhaité : ArrayList, String, etc. Nous explorerons cette méthode plus en détail dans la méthode collect et la classe Collectors à la page 52.
C'est le nom de la sortie, ils sont désormais en majuscules et séparés par des virgules.
Copiez le code comme suit :
BRIAN, NATE, NEAL, RAJU, SARA, SCOTT
Résumer
Les collections sont très courantes en programmation. Avec les expressions lambda, les opérations de collection de Java sont devenues plus simples et plus faciles. Tout l’ancien code maladroit pour les opérations de collecte peut être remplacé par cette nouvelle approche élégante et concise. L'itérateur interne rend le parcours et la transformation des collections plus pratiques, sans les problèmes de variabilité, et la recherche d'éléments de collection devient extrêmement facile. Vous pouvez écrire beaucoup moins de code en utilisant ces nouvelles méthodes. Cela rend le code plus facile à maintenir, plus axé sur la logique métier et moins sur les opérations de programmation basiques.
Dans le chapitre suivant, nous verrons comment les expressions lambda simplifient une autre opération de base dans le développement de programmes : la manipulation de chaînes et la comparaison d'objets.