Collect() Rassemblement des Éléments du Flux dans une Collection
Vous connaissez déjà les opérations terminales et vous les avez même utilisées dans des exemples et exercices précédents. Il est maintenant temps d'examiner de plus près leur fonctionnement. Commençons par la méthode collect(), qui constitue l'une des opérations terminales clés de la Stream API.
La méthode collect()
Il s'agit de l'un des outils les plus puissants lors de la manipulation de flux, permettant d'accumuler les résultats dans une List, un Set ou une Map, ainsi que d'effectuer des regroupements complexes et des calculs statistiques.
Il existe deux implémentations de la méthode collect()—explorons-les toutes les deux.
Utilisation de collect() avec des interfaces fonctionnelles
La méthode collect() dans l’API Stream peut être utilisée avec trois interfaces fonctionnelles afin d’offrir un contrôle total sur la collecte des données :
Supplier<R> supplier– crée une collection vide (R) où les éléments seront stockés. Par exemple,ArrayList::newinitialise une nouvelle liste ;BiConsumer<R, ? super T> accumulator– ajoute les éléments du flux (T) à la collection (R). Par exemple,List::addajoute des éléments à une liste ;BiConsumer<R, R> combiner– fusionne deux collections lors de l’utilisation du traitement parallèle. Par exemple,List::addAllcombine plusieurs listes en une seule.
Les trois composants fonctionnent ensemble pour offrir une flexibilité dans la collecte des données. Tout d’abord, le supplier crée une collection vide qui sera utilisée pour accumuler les éléments du flux. Ensuite, l’accumulator ajoute chaque élément au fur et à mesure du traitement du flux. Ce processus reste simple dans un flux séquentiel.
Cependant, lors de l’utilisation de flux parallèles (parallelStream()), la situation devient plus complexe.
Le traitement des données est réparti sur plusieurs threads, chaque thread créant sa propre collection distincte. Une fois le traitement terminé, ces collections individuelles doivent être fusionnées en un résultat unique. C'est ici qu'intervient le combiner, qui permet de combiner efficacement les différentes parties en une collection unifiée.
Exemple pratique
Vous travaillez pour une boutique en ligne et disposez d'une liste de produits. Votre tâche consiste à collecter uniquement les produits dont le prix est supérieur à 500 $ en utilisant la méthode collect() avec trois paramètres.
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152package com.example; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { // Initial list of products List<Product> productList = List.of( new Product("Laptop", 1200.99), new Product("Phone", 599.49), new Product("Headphones", 199.99), new Product("Monitor", 299.99), new Product("Tablet", 699.99) ); // Filtering and collecting products over $500 using `collect()` List<Product> expensiveProducts = productList.parallelStream() .filter(product -> product.getPrice() > 500) // Keep only expensive products .collect( ArrayList::new, // Create a new list (list, product) -> list.add(product), // Add each product to the list ArrayList::addAll // Merge lists (if the stream is parallel) ); // Print the result System.out.print("Products over $500: " + expensiveProducts); } } class Product { private String name; private double price; Product(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } @Override public String toString() { return name + " ($" + price + ")"; } }
La méthode collect() prend trois arguments, chacun définissant une étape différente pour rassembler les éléments dans une liste :
-
ArrayList::new(Supplier) → crée uneArrayList<Product>vide pour stocker les résultats ; -
(list, product) -> list.add(product)(BiConsumer) → ajoute chaqueProductà la liste si la condition de filtrage est remplie (price > 500) ; -
ArrayList::addAll(BiConsumer) → fusionne plusieurs listes lors de l'utilisation de streams parallèles, garantissant que tous les produits filtrés sont combinés dans une seule liste.
Même si le troisième paramètre est principalement destiné au traitement parallèle, il est requis par collect().
Utilisation de collect() avec l'interface Collector
En plus de fonctionner avec trois interfaces fonctionnelles, la méthode collect() dans la Stream API peut également être utilisée avec des implémentations prédéfinies de l'interface Collector.
Cette approche est plus flexible et pratique car elle fournit des méthodes intégrées pour travailler avec les collections.
L'interface Collector<T, A, R> se compose de plusieurs méthodes clés :
Supplier<A> supplier()– crée un conteneur vide pour accumuler les éléments ;BiConsumer<A, T> accumulator()– définit comment les éléments sont ajoutés au conteneur ;BinaryOperator<A> combiner()– fusionne deux conteneurs lors de l'utilisation du traitement parallèle ;Function<A, R> finisher()– transforme le conteneur en résultat final.
Comme vous pouvez le constater, cette structure est similaire à la méthode collect() qui fonctionne avec des interfaces fonctionnelles, mais elle introduit la méthode finisher(). Cette étape supplémentaire permet un traitement additionnel des données collectées avant de retourner le résultat final—par exemple, trier la liste avant de la retourner.
De plus, l’interface Collector fournit la méthode characteristics(), qui définit des propriétés permettant d’optimiser l’exécution des flux :
Ces caractéristiques aident l’API Stream à optimiser les performances. Par exemple, si une collection est par nature non ordonnée, spécifier UNORDERED peut éviter un tri inutile, rendant l’opération plus efficace.
Exemple pratique
Supposons que vous gériez une boutique en ligne et que vous deviez traiter les prix des produits avant de les collecter. Par exemple, vous souhaitez arrondir chaque prix à l’entier le plus proche, supprimer les doublons et trier la liste finale.
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455package com.example; import java.util.*; import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<Double> prices = List.of(1200.99, 599.49, 199.99, 599.49, 1200.49, 200.0); // Using a custom `Collector` to round prices, remove duplicates, and sort List<Integer> processedPrices = prices.parallelStream() .collect(new RoundedSortedCollector()); System.out.println("Processed product prices: " + processedPrices); } } // Custom `Collector` that rounds prices, removes duplicates, and sorts them class RoundedSortedCollector implements Collector<Double, Set<Integer>, List<Integer>> { @Override public Supplier<Set<Integer>> supplier() { // Creates a `HashSet` to store unique rounded values return HashSet::new; } @Override public BiConsumer<Set<Integer>, Double> accumulator() { // Rounds price and adds to the set return (set, price) -> set.add((int) Math.round(price)); } @Override public BinaryOperator<Set<Integer>> combiner() { return (set1, set2) -> { set1.addAll(set2); // Merges two sets return set1; }; } @Override public Function<Set<Integer>, List<Integer>> finisher() { return set -> set.stream() .sorted() // Sorts the final list .toList(); } @Override public Set<Characteristics> characteristics() { // Order is not important during accumulation return Set.of(Characteristics.UNORDERED); } }
Le traitement des données commence par leur passage dans un Collector personnalisé nommé RoundedSortedCollector.
Ce collecteur accumule d'abord tous les prix dans un Set<Integer>, garantissant ainsi que les doublons sont automatiquement supprimés. Avant d'ajouter chaque valeur, il arrondit le prix à l'aide de Math.round(price) et le convertit en int. Par exemple, 1200.99 et 1200.49 deviendront tous deux 1200, tandis que 199.99 sera arrondi à 200.
Si le flux fonctionne en mode parallèle, la méthode combiner() fusionne deux ensembles en ajoutant tous les éléments de l'un dans l'autre. Cette étape est essentielle dans les environnements multithread.
À l'étape finale, après la collecte de tous les prix, la méthode finisher() transforme l'ensemble en une liste triée. Elle convertit le Set<Integer> en flux, applique sorted() pour organiser les valeurs par ordre croissant, puis les collecte dans une List<Integer>.
Le résultat est une liste triée de prix uniques et arrondis, utilisable pour des calculs ultérieurs ou à des fins d'affichage.
1. Que fait la méthode collect() dans l'API Stream ?
2. Quelle capacité supplémentaire l'interface Collector offre-t-elle par rapport à collect() avec des interfaces fonctionnelles ?
Merci pour vos commentaires !
Demandez à l'IA
Demandez à l'IA
Posez n'importe quelle question ou essayez l'une des questions suggérées pour commencer notre discussion
Awesome!
Completion rate improved to 2.33
Collect() Rassemblement des Éléments du Flux dans une Collection
Glissez pour afficher le menu
Vous connaissez déjà les opérations terminales et vous les avez même utilisées dans des exemples et exercices précédents. Il est maintenant temps d'examiner de plus près leur fonctionnement. Commençons par la méthode collect(), qui constitue l'une des opérations terminales clés de la Stream API.
La méthode collect()
Il s'agit de l'un des outils les plus puissants lors de la manipulation de flux, permettant d'accumuler les résultats dans une List, un Set ou une Map, ainsi que d'effectuer des regroupements complexes et des calculs statistiques.
Il existe deux implémentations de la méthode collect()—explorons-les toutes les deux.
Utilisation de collect() avec des interfaces fonctionnelles
La méthode collect() dans l’API Stream peut être utilisée avec trois interfaces fonctionnelles afin d’offrir un contrôle total sur la collecte des données :
Supplier<R> supplier– crée une collection vide (R) où les éléments seront stockés. Par exemple,ArrayList::newinitialise une nouvelle liste ;BiConsumer<R, ? super T> accumulator– ajoute les éléments du flux (T) à la collection (R). Par exemple,List::addajoute des éléments à une liste ;BiConsumer<R, R> combiner– fusionne deux collections lors de l’utilisation du traitement parallèle. Par exemple,List::addAllcombine plusieurs listes en une seule.
Les trois composants fonctionnent ensemble pour offrir une flexibilité dans la collecte des données. Tout d’abord, le supplier crée une collection vide qui sera utilisée pour accumuler les éléments du flux. Ensuite, l’accumulator ajoute chaque élément au fur et à mesure du traitement du flux. Ce processus reste simple dans un flux séquentiel.
Cependant, lors de l’utilisation de flux parallèles (parallelStream()), la situation devient plus complexe.
Le traitement des données est réparti sur plusieurs threads, chaque thread créant sa propre collection distincte. Une fois le traitement terminé, ces collections individuelles doivent être fusionnées en un résultat unique. C'est ici qu'intervient le combiner, qui permet de combiner efficacement les différentes parties en une collection unifiée.
Exemple pratique
Vous travaillez pour une boutique en ligne et disposez d'une liste de produits. Votre tâche consiste à collecter uniquement les produits dont le prix est supérieur à 500 $ en utilisant la méthode collect() avec trois paramètres.
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152package com.example; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { // Initial list of products List<Product> productList = List.of( new Product("Laptop", 1200.99), new Product("Phone", 599.49), new Product("Headphones", 199.99), new Product("Monitor", 299.99), new Product("Tablet", 699.99) ); // Filtering and collecting products over $500 using `collect()` List<Product> expensiveProducts = productList.parallelStream() .filter(product -> product.getPrice() > 500) // Keep only expensive products .collect( ArrayList::new, // Create a new list (list, product) -> list.add(product), // Add each product to the list ArrayList::addAll // Merge lists (if the stream is parallel) ); // Print the result System.out.print("Products over $500: " + expensiveProducts); } } class Product { private String name; private double price; Product(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } @Override public String toString() { return name + " ($" + price + ")"; } }
La méthode collect() prend trois arguments, chacun définissant une étape différente pour rassembler les éléments dans une liste :
-
ArrayList::new(Supplier) → crée uneArrayList<Product>vide pour stocker les résultats ; -
(list, product) -> list.add(product)(BiConsumer) → ajoute chaqueProductà la liste si la condition de filtrage est remplie (price > 500) ; -
ArrayList::addAll(BiConsumer) → fusionne plusieurs listes lors de l'utilisation de streams parallèles, garantissant que tous les produits filtrés sont combinés dans une seule liste.
Même si le troisième paramètre est principalement destiné au traitement parallèle, il est requis par collect().
Utilisation de collect() avec l'interface Collector
En plus de fonctionner avec trois interfaces fonctionnelles, la méthode collect() dans la Stream API peut également être utilisée avec des implémentations prédéfinies de l'interface Collector.
Cette approche est plus flexible et pratique car elle fournit des méthodes intégrées pour travailler avec les collections.
L'interface Collector<T, A, R> se compose de plusieurs méthodes clés :
Supplier<A> supplier()– crée un conteneur vide pour accumuler les éléments ;BiConsumer<A, T> accumulator()– définit comment les éléments sont ajoutés au conteneur ;BinaryOperator<A> combiner()– fusionne deux conteneurs lors de l'utilisation du traitement parallèle ;Function<A, R> finisher()– transforme le conteneur en résultat final.
Comme vous pouvez le constater, cette structure est similaire à la méthode collect() qui fonctionne avec des interfaces fonctionnelles, mais elle introduit la méthode finisher(). Cette étape supplémentaire permet un traitement additionnel des données collectées avant de retourner le résultat final—par exemple, trier la liste avant de la retourner.
De plus, l’interface Collector fournit la méthode characteristics(), qui définit des propriétés permettant d’optimiser l’exécution des flux :
Ces caractéristiques aident l’API Stream à optimiser les performances. Par exemple, si une collection est par nature non ordonnée, spécifier UNORDERED peut éviter un tri inutile, rendant l’opération plus efficace.
Exemple pratique
Supposons que vous gériez une boutique en ligne et que vous deviez traiter les prix des produits avant de les collecter. Par exemple, vous souhaitez arrondir chaque prix à l’entier le plus proche, supprimer les doublons et trier la liste finale.
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455package com.example; import java.util.*; import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<Double> prices = List.of(1200.99, 599.49, 199.99, 599.49, 1200.49, 200.0); // Using a custom `Collector` to round prices, remove duplicates, and sort List<Integer> processedPrices = prices.parallelStream() .collect(new RoundedSortedCollector()); System.out.println("Processed product prices: " + processedPrices); } } // Custom `Collector` that rounds prices, removes duplicates, and sorts them class RoundedSortedCollector implements Collector<Double, Set<Integer>, List<Integer>> { @Override public Supplier<Set<Integer>> supplier() { // Creates a `HashSet` to store unique rounded values return HashSet::new; } @Override public BiConsumer<Set<Integer>, Double> accumulator() { // Rounds price and adds to the set return (set, price) -> set.add((int) Math.round(price)); } @Override public BinaryOperator<Set<Integer>> combiner() { return (set1, set2) -> { set1.addAll(set2); // Merges two sets return set1; }; } @Override public Function<Set<Integer>, List<Integer>> finisher() { return set -> set.stream() .sorted() // Sorts the final list .toList(); } @Override public Set<Characteristics> characteristics() { // Order is not important during accumulation return Set.of(Characteristics.UNORDERED); } }
Le traitement des données commence par leur passage dans un Collector personnalisé nommé RoundedSortedCollector.
Ce collecteur accumule d'abord tous les prix dans un Set<Integer>, garantissant ainsi que les doublons sont automatiquement supprimés. Avant d'ajouter chaque valeur, il arrondit le prix à l'aide de Math.round(price) et le convertit en int. Par exemple, 1200.99 et 1200.49 deviendront tous deux 1200, tandis que 199.99 sera arrondi à 200.
Si le flux fonctionne en mode parallèle, la méthode combiner() fusionne deux ensembles en ajoutant tous les éléments de l'un dans l'autre. Cette étape est essentielle dans les environnements multithread.
À l'étape finale, après la collecte de tous les prix, la méthode finisher() transforme l'ensemble en une liste triée. Elle convertit le Set<Integer> en flux, applique sorted() pour organiser les valeurs par ordre croissant, puis les collecte dans une List<Integer>.
Le résultat est une liste triée de prix uniques et arrondis, utilisable pour des calculs ultérieurs ou à des fins d'affichage.
1. Que fait la méthode collect() dans l'API Stream ?
2. Quelle capacité supplémentaire l'interface Collector offre-t-elle par rapport à collect() avec des interfaces fonctionnelles ?
Merci pour vos commentaires !