Menu

Les Streams Java : filter, map, collect et reduce expliqués

Comment traiter des collections avec la Stream API de Java - filter, map, sorted, collect, count et reduce - en construisant des pipelines lisibles plutôt que des boucles manuelles.

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

Des pipelines plutôt que des boucles

Un stream est un pipeline qui traite une séquence de valeurs. Vous partez d'une source - généralement une collection - vous enchaînez des opérations comme « ne garde que ceux-ci », « transforme chacun », « trie-les », et vous terminez en collectant le résultat. Au lieu d'écrire une boucle avec une liste temporaire et un if à l'intérieur, vous décrivez ce que vous voulez et laissez le stream faire le parcours.

Maintenant que vous avez les lambdas, les streams prennent tout leur sens : chaque opération reçoit une lambda (ou une référence de méthode) décrivant le travail à effectuer sur un seul élément.

Lisez de haut en bas : prends les noms, garde les longs, mets-les en majuscules, collecte-les dans une liste. La liste names d'origine n'est jamais modifiée - un stream produit un nouveau résultat et laisse la source intacte.

L'anatomie d'un stream

Tout pipeline comporte trois parties :

  • Une source - list.stream(), Arrays.stream(array) ou Stream.of(a, b, c).
  • Zéro ou plusieurs opérations intermédiaires - filter, map, sorted, distinct, limit. Chacune renvoie un autre stream, ce qui vous permet de les enchaîner.
  • Exactement une opération terminale - collect, count, forEach, reduce, findFirst. C'est elle qui déclenche le travail et produit un résultat (ou un effet de bord).

Cette distinction est importante à cause d'une règle qui surprend tout le monde au début.

Les opérations intermédiaires sont paresseuses

Les opérations intermédiaires ne font rien par elles-mêmes. Elles se contentent d'enregistrer ce qui devrait se passer. Le pipeline ne s'exécute que lorsqu'une opération terminale réclame un résultat.

Exécutez-le : la lambda de map ne s'exécute jamais. Ajoutez une opération terminale et toute la chaîne s'anime :

Cette paresse n'est pas une bizarrerie à combattre - elle permet au stream de fusionner les opérations et d'éviter le travail dont il n'a pas besoin. Mais elle signifie qu'un pipeline sans opération terminale ne fait rien, ce qui est un bug courant du type « pourquoi rien ne se passe-t-il ? ».

filter et map : garder et transformer

Ces deux-là supportent l'essentiel de la charge. filter reçoit un Predicate (une lambda renvoyant un boolean) et garde les éléments qui passent. map reçoit une Function et remplace chaque élément par le résultat de son application.

Notez que map peut changer le type de l'élément : ici, un stream de String devient un stream d'Integer. Le String::length est une référence de méthode - un raccourci pour la lambda w -> w.length().

Les opérations terminales produisent un résultat

Une fois le stream façonné, une opération terminale le transforme en quelque chose de concret :

Opérations terminales courantes : collect (rassembler dans une liste/un ensemble/une map), count, forEach, anyMatch / allMatch / noneMatch, findFirst, min / max et reduce. Une fois qu'une terminale s'exécute, le stream est consommé - vous ne pouvez pas le réutiliser. Rappelez list.stream() pour obtenir un nouveau pipeline.

Collecter les résultats

collect avec Collectors est l'outil de prédilection pour construire un résultat. Le plus courant est Collectors.toList() :

Collectors vous fournit aussi toSet(), joining(...), groupingBy(...) et counting(). Sur Java 16 et versions ultérieures, vous pouvez remplacer collect(Collectors.toList()) par le plus court .toList() (il renvoie une liste non modifiable) :

List<String> result = names.stream().map(String::toUpperCase).toList();

sorted, distinct et limit

Ces opérations intermédiaires remodèlent le stream avant que vous ne le collectiez :

sorted() sans argument utilise l'ordre naturel ; passez un Comparator (ici une lambda) pour un ordre personnalisé. Comparator.reverseOrder() est la façon la plus propre de trier par ordre décroissant.

reduce : replier un stream en une seule valeur

Lorsque vous devez combiner tous les éléments en un seul résultat - une somme, un produit, la chaîne la plus longue - reduce est l'outil général. Vous lui donnez une valeur de départ et une fonction qui fusionne deux valeurs :

Pour les sommes et les moyennes simples, les streams spécialisés sont plus clairs : nums.stream().mapToInt(Integer::intValue).sum(). Recourez à reduce lorsqu'il n'existe pas d'agrégateur tout prêt.

Les streams ne remplacent pas toutes les boucles

Les streams brillent quand vous transformez une collection en un résultat. Ils ne sont pas automatiquement plus rapides qu'une boucle, et ils deviennent maladroits quand vous devez modifier un état externe ou sortir prématurément de manières compliquées. Une bonne règle : si le pipeline se lit comme une phrase claire, utilisez un stream ; si vous avez recours à un compteur partagé ou à un index, une simple boucle for est honnête et tout à fait acceptable.

Rappelez-vous aussi qu'un stream est à usage unique. Ceci lève une exception :

Stream<String> s = names.stream();
s.forEach(System.out::println);
s.count();   // IllegalStateException: stream has already been operated upon

Suite : Optional

Plusieurs terminales de stream - findFirst, min, max, reduce sans identité - peuvent ne rien trouver, donc elles ne renvoient pas une valeur nue. Elles renvoient un Optional, le conteneur de Java pour « peut-être une valeur, peut-être rien », qui vous offre enfin une alternative propre au renvoi de null. C'est la page suivante.

Questions fréquentes

Qu'est-ce qu'un stream en Java ?

Un stream est un pipeline qui traite une séquence d'éléments - provenant généralement d'une collection - à travers une chaîne d'opérations comme filter, map et sorted, et se terminant par une opération terminale comme collect ou count. Il ne stocke pas de données et ne modifie pas la source ; il décrit un calcul. On en crée un avec list.stream() et on lit la chaîne de haut en bas, comme une recette.

Comment reconvertir un stream en liste en Java ?

Terminez le pipeline par un collecteur terminal : list.stream().filter(...).collect(Collectors.toList()). Sur Java 16+, vous pouvez utiliser le plus court .toList(), qui renvoie une liste non modifiable. Sans opération terminale, rien ne s'exécute du tout, car les opérations intermédiaires comme filter et map sont paresseuses.

Quand utiliser un stream plutôt qu'une boucle for ?

Optez pour un stream lorsque vous transformez ou filtrez une collection en un résultat - le pipeline se lit comme une phrase claire ("prends les noms, garde les longs, mets-les en majuscules, collecte-les dans une liste"). Restez sur une simple boucle for lorsque vous devez modifier un état externe, sortir prématurément de manières complexes, ou lorsque la logique est plus simple sous forme d'étapes impératives. Les streams visent la clarté, pas la vitesse brute.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER