Java Collect(): come utilizzare le operazioni di riduzione
Gli Stream Collector sono una potente funzionalità dell’API Stream di Java 8, che permette di raccogliere ed elaborare i dati in modo efficiente. Ti spieghiamo la struttura e le possibilità di applicazione del metodo collect
in Java.
Quali sono i campi di applicazione per Java Collect()?
Uno Stream Collector può essere utilizzato per creare una lista, un set o una mappa partendo da uno stream. Una mappa è una sequenza di elementi che vengono elaborati uno dopo l’altro. L’interfaccia Collector offre una serie di operazioni di riduzione per i dati in una pipeline di stream. Si tratta di operazioni da terminale che raccolgono e uniscono i risultati di passaggi intermedi.
Ad esempio, i Collector possono essere utilizzati per filtrare o ordinare gli oggetti di uno stream. È possibile eseguire anche l’operazione di aggregazione, ossia la somma di numeri, l’unione di stringhe o il conteggio di elementi. Inoltre, i Collector dispongono di funzioni per trasformare i contenuti di uno stream in una data struttura. In questo modo è possibile, ad esempio, convertire una lista in una mappa. I raggruppamenti aiutano a categorizzare gli elementi con determinate caratteristiche o condizioni. Ma, soprattutto, gli Stream Collector hanno il vantaggio di elaborare i dati in parallelo per mezzo di più thread. In questo modo permettono di eseguire le operazioni molto più velocemente e con molta più efficienza, in particolare in presenza di grandi quantità di dati.
La sintassi di Java Collect()
Questo metodo accetta un Collector come argomento che descrive il modo in cui gli elementi dello stream devono essere raccolti e aggregati. Un Collector è un’interfaccia che mette a disposizione diversi metodi per unire gli elementi dello stream in un data forma, ad esempio in una lista, un set o una mappa.
Esistono due tipi di metodi Java Collect:
- <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner)
- <R, A> R collect(Collector<? super T, A, R> collector)
La prima variante ha tre funzioni come argomento:
- supplier: crea un contenitore utilizzato per i risultati intermedi.
- accumulator: calcola il risultato finale.
- combiner: combina i risultati di operazioni di stream parallele.
Questi Collector predefiniti sono già inclusi nella libreria standard e possono essere importati e utilizzati facilmente.
La seconda variante utilizza come argomento un Collector e restituisce il risultato.
- R: il tipo di risultato.
- T: il tipo degli elementi nello stream.
- A: il tipo dell’accumulatore che memorizza lo stato intermedio dell’operazione Collector.
- collector: esegue l’operazione di riduzione.
Utilizzando questa variante, gli sviluppatori possono creare Collector su misura pensati appositamente per le loro esigenze, offrendo maggiore flessibilità e controllo sul processo di riduzione.
Esempi pratici di utilizzo di Java Collect()
Nelle parti seguenti ti mostriamo diverse funzioni del metodo Stream.collect()
. Prima di iniziare a usare il Collection Framework, ti consigliamo di prendere dimestichezza con gli operatori Java.
Concatenazione di una lista di stringhe
Con Java Collect() puoi concatenare una lista di stringhe per ottenere una nuova stringa:
List<String> letters = List.of("a", "b", "c", "d", "e");
// without combiner function
StringBuilder result = letters.stream().collect(StringBuilder::new, (x, y) -> x.append(y),
(a, b) -> a.append(",").append(b));
System.out.println(result.toString());
// with combiner function
StringBuilder result1 = letters.parallelStream().collect(StringBuilder::new, (x, y) -> x.append(y),
(a, b) -> a.append(",").append(b));
System.out.println(result1.toString());
JavaIl risultato che otteniamo è il seguente:
abcde
a, b, c, d, e
JavaCon il primo calcolo è presente soltanto un’istanza StringBuilder
e non è stata utilizzata alcuna funzione Combiner
. Per questo motivo il risultato è abcde
.
Il secondo output mostra come la funzione Combiner
unisce le istanze StringBuilder
e le separa con una virgola.
Raccolta di elementi in una lista con toList()
È possibile utilizzare la funzione filter()
per selezionare determinati elementi e memorizzarli in una nuova lista con toList()
.
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);
List<Integer> oddNumbers = numbers.stream().filter(x -> x % 2 != 0).collect(Collectors.toList());
System.out.println(oddNumbers);
JavaLa nuova lista contiene quindi esclusivamente numeri dispari:
[1, 3, 5, 7]
JavaRaccolta di elementi in un set con toSet()
Analogamente è possibile creare un nuovo set partendo da elementi selezionati. La sequenza può essere non ordinata in un set.
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);
Set<Integer> evenNumbers = numbers.parallelStream().filter(x -> x % 2 == 0).collect(Collectors.toSet());
System.out.println(evenNumbers);
JavaIl risultato è il seguente:
[2, 4, 6]
JavaRaccolta di elementi in una mappa con toMap()
L’uso di una mappa in combinazione con Java Collect() permette di assegnare un valore a ciascuna chiave.
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);
Map<Integer, String> mapEvenNumbers = numbers.parallelStream().filter(x -> x % 2 == 0)
.collect(Collectors.toMap(Function.identity(), x -> String.valueOf(x)));
System.out.println(mapEvenNumbers);
JavaIl risultato mostra come l’input, composto da numeri pari, è stato assegnato ai suoi valori identici:
{2=2, 4=4, 6=6}
JavaCombinazione di elementi in una catena di caratteri con joining()
Il metodo joining()
combina ciascun elemento dello stream nell’ordine in cui appare e utilizza un carattere separatore per separare gli elementi. Il separatore può essere passato a joining()
come argomento. Se non si indica nessun separatore, joining()
utilizza la stringa vuota ""
.
jshell> String result1 = Stream.of("a", "b", "c").collect(Collectors.joining());
jshell> String result2 = Stream.of("a", "b", "c").collect(Collectors.joining(",", "{", "}"));
JavaI risultati sono:
result1 ==> "abc"
result2 ==> "{a,b,c}"
Java