CodeGym /Corsi /JAVA 25 SELF /Metodi reduce e collect: aggregazione dei dati

Metodi reduce e collect: aggregazione dei dati

JAVA 25 SELF
Livello 31, Lezione 1
Disponibile

1. Metodo reduce: riduzione universale

In programmazione spesso occorre «ridurre» una collezione a un unico valore finale: calcolare una somma, trovare un prodotto, concatenare stringhe, computare una metrica aggregata o raccogliere elementi in una nuova struttura. Prima si faceva a mano con cicli e una variabile accumulatore. Oggi lo Stream API offre modi eleganti — i metodi universali reduce e collect, che permettono di scrivere codice compatto e dichiarativo.

  • reduce — riduce lo stream a un singolo valore finale (somma, prodotto, concatenazione, ecc.).
  • collect — trasforma lo stream in una collezione, stringa, mappa o una struttura arbitraria.

Procediamo con ordine.

A cosa serve reduce?

reduce è un metodo terminale che «riduce» gli elementi dello stream a un unico valore usando una funzione accumulatrice. Concettualmente equivale a un passaggio sulla collezione con accumulo passo passo del risultato.

Firme del metodo reduce

Nello Stream API ci sono tre varianti principali di reduce():

Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
  • accumulator — una funzione che prende il valore accumulato corrente e l’elemento successivo e restituisce il nuovo risultato.
  • identity — il valore iniziale dell’accumulatore (ad esempio, 0 per la somma, 1 per il prodotto).
  • combiner — usato negli stream paralleli per unire i risultati intermedi.

Esempi di utilizzo di reduce

Esempio 1: Somma dei numeri

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

// reduce senza identity — restituisce Optional
Optional<Integer> sum1 = numbers.stream()
    .reduce((a, b) -> a + b);

System.out.println(sum1.orElse(0)); // 15

// reduce con identity — il risultato esiste sempre
int sum2 = numbers.stream()
    .reduce(0, (a, b) -> a + b);

System.out.println(sum2); // 15

Esempio 2: Prodotto di tutti i numeri

int product = numbers.stream()
    .reduce(1, (a, b) -> a * b);
System.out.println(product); // 120

Esempio 3: Concatenazione di stringhe

List<String> words = List.of("Java", "Stream", "API");
String phrase = words.stream()
    .reduce("", (a, b) -> a + " " + b);
System.out.println(phrase.trim()); // Java Stream API

Esempio 4: Ricerca dell'elemento massimo

Optional<Integer> max = numbers.stream()
    .reduce(Integer::max);
max.ifPresent(System.out::println); // 5

Esempio 5: Somma delle lunghezze di tutte le stringhe

List<String> texts = List.of("gatto", "cane", "elefante");
int totalLength = texts.stream()
    .map(String::length)
    .reduce(0, Integer::sum);
System.out.println(totalLength); // 17

Come funziona reduce

La logica di reduce è equivalente al seguente ciclo:

T result = identity;
for (T element : collection) {
    result = accumulator.apply(result, element);
}
return result;

Se identity non è specificato, il valore iniziale è il primo elemento dello stream e il metodo restituisce un Optional (può essere vuoto se lo stream è vuoto).

2. Metodo collect: trasformazione universale

collect è un metodo terminale che trasforma lo stream in una collezione, stringa, mappa o qualsiasi altra struttura. Per farlo usa i «collector» (Collector) che descrivono il processo di raccolta. Nella maggior parte dei casi usiamo quelli pronti della classe Collectors.

I collector più popolari

  • Collectors.toList() — raccoglie gli elementi in una List.
  • Collectors.toSet() — raccoglie gli elementi in un Set.
  • Collectors.toMap() — raccoglie gli elementi in una Map.
  • Collectors.joining() — incolla le stringhe in un’unica stringa.
  • Collectors.groupingBy() — raggruppa gli elementi in base a un criterio.
  • Collectors.counting() — conta il numero di elementi.
  • Collectors.summarizingInt() — raccoglie statistiche sui numeri (somma, media, min/max).

Esempi di utilizzo di collect

Esempio 1: Raccolta in una lista

List<String> names = List.of("Anya", "Boris", "Vasya", "Anya");
List<String> uniqueNames = names.stream()
    .distinct()
    .collect(Collectors.toList());
System.out.println(uniqueNames); // [Anya, Boris, Vasya]

Esempio 2: Raccolta in un insieme (Set)

Set<String> nameSet = names.stream()
    .collect(Collectors.toSet());
System.out.println(nameSet); // [Anya, Boris, Vasya] (l'ordine non è garantito)

Esempio 3: Raccolta in una stringa

String csv = names.stream()
    .collect(Collectors.joining(", "));
System.out.println(csv); // Anya, Boris, Vasya, Anya

Esempio 4: Raccolta in Map

Supponiamo di avere una classe:

public class Employee {
    private String name;
    private String department;
    public Employee(String name, String department) {
        this.name = name;
        this.department = department;
    }
    public String getName() { return name; }
    public String getDepartment() { return department; }
}

Raccogliamo una mappa «nome → reparto»:

List<Employee> employees = List.of(
    new Employee("Anya", "IT"),
    new Employee("Boris", "HR"),
    new Employee("Vasya", "IT")
);

Map<String, String> nameToDept = employees.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        Employee::getDepartment,
        (oldValue, newValue) -> newValue // gestione dei duplicati dei nomi
    ));

System.out.println(nameToDept); // {Anya=IT, Boris=HR, Vasya=IT}

Esempio 5: Raccogliere elementi unici in un Set

Set<String> unique = names.stream()
    .collect(Collectors.toSet());
System.out.println(unique);

Esempio 6: Raccogliere una statistica sui numeri

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
IntSummaryStatistics stats = numbers.stream()
    .collect(Collectors.summarizingInt(Integer::intValue));
System.out.println(stats.getSum());     // 15
System.out.println(stats.getAverage()); // 3.0
System.out.println(stats.getMax());     // 5
System.out.println(stats.getMin());     // 1

3. Confronto: quando usare reduce e quando — collect

reduce — quando serve ottenere un unico valore finale tramite un’operazione binaria: somma, prodotto, massimo, concatenazione.

collect — quando occorre raccogliere elementi in una collezione/mappa/stringa o eseguire un’aggregazione più complessa con un Collector. Per questi casi collect è di solito più potente ed efficiente.

Tabella: reduce vs collect

Compito Cosa usare Esempio
Somma dei numeri
reduce
reduce(0, Integer::sum)
Prodotto
reduce
reduce(1, (a, b) -> a * b)
Raccolta in List
collect
collect(Collectors.toList())
Raccolta in Map
collect
collect(Collectors.toMap(...))
Raggruppamento
collect
collect(Collectors.groupingBy(...))
Concatenazione di stringhe reduce / collect reduce("", String::concat) oppure Collectors.joining()

4. Esercizi pratici

Compito 1: Trovare la somma delle lunghezze di tutte le stringhe in una lista

List<String> words = List.of("gatto", "cane", "elefante");

int totalLength = words.stream()
    .mapToInt(String::length)
    .sum(); // oppure con reduce: .reduce(0, Integer::sum)

System.out.println(totalLength); // 17

Compito 2: Raccogliere gli elementi unici in un Set

List<String> fruits = List.of("mela", "pera", "mela", "arancia");

Set<String> uniqueFruits = fruits.stream()
    .collect(Collectors.toSet());

System.out.println(uniqueFruits); // [mela, pera, arancia]

Compito 3: Costruire una Map da una lista di oggetti

List<Employee> employees = List.of(
    new Employee("Anya", "IT"),
    new Employee("Boris", "HR"),
    new Employee("Vasya", "IT")
);

Map<String, String> nameToDept = employees.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        Employee::getDepartment,
        (oldValue, newValue) -> newValue // se i nomi coincidono
    ));

System.out.println(nameToDept);

Compito 4: Raccogliere tutti i nomi separati da virgola

String allNames = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.joining(", "));

System.out.println(allNames); // Anya, Boris, Vasya

5. Dettagli di implementazione e sfumature

Optional e reduce

Se si usa reduce senza identity, il risultato è un Optional. È sicuro: se lo stream è vuoto, anche il risultato è vuoto. Ricordate di gestirlo correttamente: ifPresent(...), orElse(...), orElseThrow(...).

Optional<Integer> max = numbers.stream().reduce(Integer::max);
max.ifPresent(System.out::println);

Collector personalizzati: se proprio serve

Potete scrivere un vostro Collector se quelli standard non bastano. Ma per il 99% dei casi bastano quelli pronti in Collectors.

Collector e stream paralleli

I collector pronti di Collectors sono progettati per funzionare correttamente con parallelStream(). Non aggiungete elementi manualmente a una collezione condivisa e mutabile dentro forEach su uno stream parallelo — incapperete in race condition.

6. Errori comuni nell'uso di reduce e collect

Errore n. 1: non controllate Optional dopo reduce. Se lo stream è vuoto, reduce senza identity restituisce un Optional vuoto. Chiamare get() porterà a NoSuchElementException. Usate ifPresent, orElse o orElseThrow.

Errore n. 2: provate a raccogliere una collezione con reduce. Si può fare, ma collect è più adatto e veloce per questo scopo:

// Inefficiente!
List<String> list = stream.reduce(
    new ArrayList<>(),
    (acc, elem) -> { acc.add(elem); return acc; },
    (acc1, acc2) -> { acc1.addAll(acc2); return acc1; }
);
// Meglio così:
List<String> list2 = stream.collect(Collectors.toList());

Errore n. 3: non gestite i duplicati delle chiavi in toMap. Se le chiavi coincidono, verrà lanciata un’eccezione. Aggiungete il terzo argomento in toMap per risolvere i conflitti.

Errore n. 4: usate collezioni mutabili negli stream paralleli senza sincronizzazione. In collect usate i collector standard — funzionano correttamente in modalità parallela. Non fate list.add() in forEach su parallelStream().

Errore n. 5: confondete reduce e collect per compiti complessi. reduce — per aggregazioni semplici (somma, massimo). collect — per la raccolta in collezioni, il raggruppamento, la costruzione di una Map e l’aggregazione complessa.

1
Compito
JAVA 25 SELF, livello 31, lezione 1
Bloccato
Ricerca della temperatura massima della settimana 🌡️
Ricerca della temperatura massima della settimana 🌡️
1
Compito
JAVA 25 SELF, livello 31, lezione 1
Bloccato
Creazione di un incantesimo magico 📜
Creazione di un incantesimo magico 📜
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION