Collect() Raccolta degli Elementi dello Stream in una Collezione
Hai già familiarità con le operazioni terminali e le hai persino utilizzate in precedenti esempi ed esercizi. Ora è il momento di esaminare più da vicino il loro funzionamento. Per prima cosa analizziamo il metodo collect(), una delle principali operazioni terminali nella Stream API.
Il metodo collect()
È uno degli strumenti più potenti quando si lavora con gli stream, poiché consente di accumulare i risultati in una List, Set o Map, oltre a eseguire raggruppamenti complessi e calcoli statistici.
Esistono due implementazioni del metodo collect()—analizziamole entrambe.
Utilizzo di collect() con le interfacce funzionali
Il metodo collect() nella Stream API può essere utilizzato con tre interfacce funzionali per offrire il pieno controllo sulla raccolta dei dati:
Supplier<R> supplier– crea una collezione vuota (R) dove verranno memorizzati gli elementi. Ad esempio,ArrayList::newinizializza una nuova lista;BiConsumer<R, ? super T> accumulator– aggiunge gli elementi dello stream (T) alla collezione (R). Ad esempio,List::addinserisce elementi in una lista;BiConsumer<R, R> combiner– unisce due collezioni quando viene utilizzato il processamento parallelo. Ad esempio,List::addAllcombina liste in una sola.
Tutti e tre i componenti lavorano insieme per offrire flessibilità nella raccolta dei dati. Per prima cosa, il supplier crea una collezione vuota che verrà utilizzata per accumulare gli elementi dallo stream. Successivamente, l'accumulator aggiunge ogni elemento man mano che lo stream li elabora. Questo flusso rimane semplice in uno stream sequenziale.
Tuttavia, quando si lavora con stream paralleli (parallelStream()), la situazione diventa più complessa.
L'elaborazione dei dati è suddivisa tra più thread, con ciascun thread che crea la propria collezione separata. Una volta completata l'elaborazione, queste singole collezioni devono essere unite in un risultato unico. Qui entra in gioco il combiner, che combina in modo efficiente le parti separate in un'unica collezione unificata.
Esempio pratico
Si lavora per un negozio online e si dispone di un elenco di prodotti. L'obiettivo è raccogliere solo i prodotti che costano più di $500 utilizzando il metodo collect() con tre parametri.
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 + ")"; } }
Il metodo collect() accetta tre argomenti, ciascuno dei quali definisce una fase diversa nella raccolta degli elementi in una lista:
-
ArrayList::new(Supplier) → crea unArrayList<Product>vuoto per memorizzare i risultati; -
(list, product) -> list.add(product)(BiConsumer) → aggiunge ogniProductalla lista se soddisfa la condizione di filtro (price > 500); -
ArrayList::addAll(BiConsumer) → unisce più liste quando si utilizzano stream paralleli, garantendo che tutti i prodotti filtrati siano combinati in un'unica lista.
Anche se il terzo parametro è principalmente per l'elaborazione parallela, è richiesto da collect().
Utilizzo di collect() con l'interfaccia Collector
Oltre a funzionare con tre interfacce funzionali, il metodo collect() nello Stream API può essere utilizzato anche con implementazioni predefinite dell'interfaccia Collector.
Questo approccio è più flessibile e conveniente poiché fornisce metodi integrati per lavorare con le collezioni.
L'interfaccia Collector<T, A, R> è composta da diversi metodi chiave:
Supplier<A> supplier()– crea un contenitore vuoto per accumulare gli elementi;BiConsumer<A, T> accumulator()– definisce come gli elementi vengono aggiunti al contenitore;BinaryOperator<A> combiner()– unisce due contenitori quando viene utilizzato il processamento parallelo;Function<A, R> finisher()– trasforma il contenitore nel risultato finale.
Come puoi vedere, questa struttura è simile al metodo collect() che lavora con le interfacce funzionali, ma introduce il metodo finisher(). Questo passaggio aggiuntivo consente una elaborazione extra sui dati raccolti prima di restituire il risultato finale—ad esempio, ordinare la lista prima di restituirla.
Inoltre, l'interfaccia Collector fornisce il metodo characteristics(), che definisce le proprietà utili per ottimizzare l'esecuzione dello stream:
Queste caratteristiche aiutano la Stream API a ottimizzare le prestazioni. Ad esempio, se una collezione è intrinsecamente non ordinata, specificare UNORDERED può evitare ordinamenti non necessari, rendendo l'operazione più efficiente.
Esempio pratico
Immagina di gestire un negozio online e di dover elaborare i prezzi dei prodotti prima di raccoglierli. Ad esempio, vuoi arrotondare ogni prezzo al numero intero più vicino, rimuovere i duplicati e ordinare la lista 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); } }
L'elaborazione dei dati inizia passando i valori a un Collector personalizzato chiamato RoundedSortedCollector.
Questo collector accumula inizialmente tutti i prezzi in un Set<Integer>, garantendo la rimozione automatica dei duplicati. Prima di aggiungere ciascun valore, il prezzo viene arrotondato utilizzando Math.round(price) e convertito in int. Ad esempio, sia 1200.99 che 1200.49 diventano 1200, mentre 199.99 viene arrotondato a 200.
Se lo stream viene eseguito in modalità parallela, il metodo combiner() unisce due set aggiungendo tutti gli elementi di un set nell'altro. Questo passaggio è fondamentale negli ambienti multi-thread.
Nella fase finale, dopo aver raccolto tutti i prezzi, il metodo finisher() trasforma il set in una lista ordinata. Converte il Set<Integer> in uno stream, applica sorted() per disporre i valori in ordine crescente e li raccoglie in una List<Integer>.
Il risultato è una lista ordinata di prezzi unici e arrotondati che può essere utilizzata per ulteriori calcoli o visualizzazioni.
1. Cosa fa il metodo collect() nello Stream API?
2. Quale capacità aggiuntiva offre l'interfaccia Collector rispetto a collect() con interfacce funzionali?
Grazie per i tuoi commenti!
Chieda ad AI
Chieda ad AI
Chieda pure quello che desidera o provi una delle domande suggerite per iniziare la nostra conversazione
Awesome!
Completion rate improved to 2.33
Collect() Raccolta degli Elementi dello Stream in una Collezione
Scorri per mostrare il menu
Hai già familiarità con le operazioni terminali e le hai persino utilizzate in precedenti esempi ed esercizi. Ora è il momento di esaminare più da vicino il loro funzionamento. Per prima cosa analizziamo il metodo collect(), una delle principali operazioni terminali nella Stream API.
Il metodo collect()
È uno degli strumenti più potenti quando si lavora con gli stream, poiché consente di accumulare i risultati in una List, Set o Map, oltre a eseguire raggruppamenti complessi e calcoli statistici.
Esistono due implementazioni del metodo collect()—analizziamole entrambe.
Utilizzo di collect() con le interfacce funzionali
Il metodo collect() nella Stream API può essere utilizzato con tre interfacce funzionali per offrire il pieno controllo sulla raccolta dei dati:
Supplier<R> supplier– crea una collezione vuota (R) dove verranno memorizzati gli elementi. Ad esempio,ArrayList::newinizializza una nuova lista;BiConsumer<R, ? super T> accumulator– aggiunge gli elementi dello stream (T) alla collezione (R). Ad esempio,List::addinserisce elementi in una lista;BiConsumer<R, R> combiner– unisce due collezioni quando viene utilizzato il processamento parallelo. Ad esempio,List::addAllcombina liste in una sola.
Tutti e tre i componenti lavorano insieme per offrire flessibilità nella raccolta dei dati. Per prima cosa, il supplier crea una collezione vuota che verrà utilizzata per accumulare gli elementi dallo stream. Successivamente, l'accumulator aggiunge ogni elemento man mano che lo stream li elabora. Questo flusso rimane semplice in uno stream sequenziale.
Tuttavia, quando si lavora con stream paralleli (parallelStream()), la situazione diventa più complessa.
L'elaborazione dei dati è suddivisa tra più thread, con ciascun thread che crea la propria collezione separata. Una volta completata l'elaborazione, queste singole collezioni devono essere unite in un risultato unico. Qui entra in gioco il combiner, che combina in modo efficiente le parti separate in un'unica collezione unificata.
Esempio pratico
Si lavora per un negozio online e si dispone di un elenco di prodotti. L'obiettivo è raccogliere solo i prodotti che costano più di $500 utilizzando il metodo collect() con tre parametri.
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 + ")"; } }
Il metodo collect() accetta tre argomenti, ciascuno dei quali definisce una fase diversa nella raccolta degli elementi in una lista:
-
ArrayList::new(Supplier) → crea unArrayList<Product>vuoto per memorizzare i risultati; -
(list, product) -> list.add(product)(BiConsumer) → aggiunge ogniProductalla lista se soddisfa la condizione di filtro (price > 500); -
ArrayList::addAll(BiConsumer) → unisce più liste quando si utilizzano stream paralleli, garantendo che tutti i prodotti filtrati siano combinati in un'unica lista.
Anche se il terzo parametro è principalmente per l'elaborazione parallela, è richiesto da collect().
Utilizzo di collect() con l'interfaccia Collector
Oltre a funzionare con tre interfacce funzionali, il metodo collect() nello Stream API può essere utilizzato anche con implementazioni predefinite dell'interfaccia Collector.
Questo approccio è più flessibile e conveniente poiché fornisce metodi integrati per lavorare con le collezioni.
L'interfaccia Collector<T, A, R> è composta da diversi metodi chiave:
Supplier<A> supplier()– crea un contenitore vuoto per accumulare gli elementi;BiConsumer<A, T> accumulator()– definisce come gli elementi vengono aggiunti al contenitore;BinaryOperator<A> combiner()– unisce due contenitori quando viene utilizzato il processamento parallelo;Function<A, R> finisher()– trasforma il contenitore nel risultato finale.
Come puoi vedere, questa struttura è simile al metodo collect() che lavora con le interfacce funzionali, ma introduce il metodo finisher(). Questo passaggio aggiuntivo consente una elaborazione extra sui dati raccolti prima di restituire il risultato finale—ad esempio, ordinare la lista prima di restituirla.
Inoltre, l'interfaccia Collector fornisce il metodo characteristics(), che definisce le proprietà utili per ottimizzare l'esecuzione dello stream:
Queste caratteristiche aiutano la Stream API a ottimizzare le prestazioni. Ad esempio, se una collezione è intrinsecamente non ordinata, specificare UNORDERED può evitare ordinamenti non necessari, rendendo l'operazione più efficiente.
Esempio pratico
Immagina di gestire un negozio online e di dover elaborare i prezzi dei prodotti prima di raccoglierli. Ad esempio, vuoi arrotondare ogni prezzo al numero intero più vicino, rimuovere i duplicati e ordinare la lista 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); } }
L'elaborazione dei dati inizia passando i valori a un Collector personalizzato chiamato RoundedSortedCollector.
Questo collector accumula inizialmente tutti i prezzi in un Set<Integer>, garantendo la rimozione automatica dei duplicati. Prima di aggiungere ciascun valore, il prezzo viene arrotondato utilizzando Math.round(price) e convertito in int. Ad esempio, sia 1200.99 che 1200.49 diventano 1200, mentre 199.99 viene arrotondato a 200.
Se lo stream viene eseguito in modalità parallela, il metodo combiner() unisce due set aggiungendo tutti gli elementi di un set nell'altro. Questo passaggio è fondamentale negli ambienti multi-thread.
Nella fase finale, dopo aver raccolto tutti i prezzi, il metodo finisher() trasforma il set in una lista ordinata. Converte il Set<Integer> in uno stream, applica sorted() per disporre i valori in ordine crescente e li raccoglie in una List<Integer>.
Il risultato è una lista ordinata di prezzi unici e arrotondati che può essere utilizzata per ulteriori calcoli o visualizzazioni.
1. Cosa fa il metodo collect() nello Stream API?
2. Quale capacità aggiuntiva offre l'interfaccia Collector rispetto a collect() con interfacce funzionali?
Grazie per i tuoi commenti!