1. Conteggio degli elementi: count()
Quando lavori con collezioni di numeri o oggetti, quasi sempre nasce l’esigenza di calcolare qualcosa: il numero di elementi, la somma, la media, il massimo o il minimo. Per esempio, il numero di dipendenti in un’azienda, la somma di tutti gli stipendi, l’età media degli studenti, il prodotto più costoso, l’ordine massimo. Con la comparsa della Stream API tutto è diventato più semplice ed espressivo.
Come funziona
Il metodo count() è un operatore terminale della Stream API che restituisce il numero di elementi nello stream.
long count = employees.stream().count();
Se devi contare solo gli elementi che soddisfano una condizione — usa il filtro filter(...):
long richCount = employees.stream()
.filter(e -> e.getSalary() > 100_000)
.count();
Esempio per la nostra applicazione:
Supponiamo che ci sia un elenco di prodotti:
List<Product> products = List.of(
new Product("Latte", 80),
new Product("Formaggio", 250),
new Product("Pane", 40)
);
Contiamo quanti prodotti costano più di 100:
long expensiveCount = products.stream()
.filter(p -> p.getPrice() > 100)
.count();
System.out.println("Prodotti costosi: " + expensiveCount);
Che cos’è Optional
In Java esiste un contenitore speciale — Optional. È un oggetto wrapper che può contenere un valore oppure essere vuoto. Si usa nei casi in cui il risultato potrebbe non esserci e ciò va reso esplicito. Per esempio, i metodi min(), max() e average() possono restituire un risultato vuoto se la collezione è vuota. Invece di null viene restituito un Optional.
Metodi principali di Optional
- isPresent() / isEmpty() — verifica se è presente un valore.
- orElse(valore) — restituisce il valore o un predefinito se vuoto.
- orElseThrow() — lancia un’eccezione se vuoto.
- ifPresent(...) — esegue un’azione solo se il valore è presente.
Esempio:
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
if (min.isPresent()) {
System.out.println("Prezzo minimo: " + min.getAsInt());
} else {
System.out.println("La lista è vuota");
}
Oppure più breve:
int minValue = min.orElse(-1);
System.out.println("Prezzo minimo: " + minValue);
Questo approccio consente di evitare NullPointerException e rende il codice più sicuro.
2. Somma, media, minimo e massimo per i dati numerici
Stream primitivi: IntStream, DoubleStream, LongStream
Per i dati numerici la Stream API offre stream speciali: IntStream, DoubleStream, LongStream. Consentono di calcolare facilmente e rapidamente somma, media, minimo e massimo.
Conversione in stream numerico
Per ottenere uno stream numerico da uno stream di oggetti, usa i metodi mapToInt, mapToDouble, mapToLong.
int sum = products.stream()
.mapToInt(Product::getPrice)
.sum();
System.out.println("Somma totale dei prezzi: " + sum);
Metodi
- sum() — somma di tutti gli elementi.
- average() — media aritmetica (restituisce OptionalDouble).
- min(), max() — minimo e massimo (restituiscono OptionalInt/OptionalDouble/OptionalLong).
Esempi:
// Somma di tutti i prezzi
int total = products.stream()
.mapToInt(Product::getPrice)
.sum();
// Prezzo medio
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
// Prezzo minimo e massimo
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
OptionalInt max = products.stream()
.mapToInt(Product::getPrice)
.max();
Come ottenere un valore da Optional
double average = avg.orElse(0.0); // 0.0 se la collezione è vuota
int minValue = min.orElse(-1); // -1 se la collezione è vuota
int maxValue = max.orElse(-1); // -1 se la collezione è vuota
Scherzo da programmatore: una lista vuota non è un errore, è solo una situazione in cui non hai dati. Ma se provi a prendere un valore da un Optional vuoto con il metodo getAsInt() — otterrai una NoSuchElementException. Usa sempre orElse o isPresent()!
3. Aggregazione di oggetti: Collectors.summingInt, averagingInt, maxBy, minBy
Se hai uno stream di oggetti e vuoi aggregare i valori in base a qualche criterio, usa gli appositi collector della classe Collectors.
summingInt, averagingInt
int totalSalary = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
double avgSalary = employees.stream()
.collect(Collectors.averagingInt(Employee::getSalary));
minBy, maxBy
Per trovare l’oggetto con il valore massimo/minimo di un campo:
Optional<Employee> richest = employees.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));
Optional<Employee> poorest = employees.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));
Importante: il risultato è un Optional, perché la collezione può essere vuota.
Esempio
Creiamo la classe Product:
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
Creiamo un elenco di prodotti:
List<Product> products = List.of(
new Product("Latte", 80),
new Product("Formaggio", 250),
new Product("Pane", 40)
);
Troviamo il prodotto più costoso:
Optional<Product> maxProduct = products.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Product::getPrice)));
maxProduct.ifPresent(p -> System.out.println("Il prodotto più costoso: " + p.getName()));
4. Combinazione con il raggruppamento
I metodi di aggregazione si combinano spesso con il raggruppamento. Per esempio, puoi trovare il prezzo medio dei prodotti in base alla prima lettera del nome:
Map<Character, Double> avgPriceByLetter = products.stream()
.collect(Collectors.groupingBy(
p -> p.getName().charAt(0),
Collectors.averagingInt(Product::getPrice)
));
System.out.println(avgPriceByLetter);
Risultato:
{L=80.0, F=250.0, P=40.0}
5. Esempi dalla vita reale
Esempio 1: Studenti e punteggi
Elenco di studenti e i loro punteggi:
public class Student {
private final String name;
private final int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
}
List<Student> students = List.of(
new Student("Alisa", 90),
new Student("Bob", 75),
new Student("Vasya", 100)
);
Quanti studenti hanno un punteggio superiore a 80?
long count = students.stream()
.filter(s -> s.getScore() > 80)
.count();
System.out.println("Studenti con punteggio > 80: " + count);
Punteggio medio:
double avg = students.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
System.out.println("Punteggio medio: " + avg);
Lo studente più bravo:
students.stream()
.max(Comparator.comparingInt(Student::getScore))
.ifPresent(s -> System.out.println("Miglior studente: " + s.getName()));
Esempio 2: Numero di prodotti unici
long uniqueCount = products.stream()
.map(Product::getName)
.distinct()
.count();
System.out.println("Prodotti unici: " + uniqueCount);
6. Dettagli utili
Schema visivo: come funzionano i metodi di aggregazione
Collezione di oggetti
│
▼
stream()
│
▼
mapToInt/Double/Long (se necessario)
│
▼
sum() / average() / min() / max() / count()
│
▼
Risultato (int, double, long, Optional)
Gestione di Optional: come estrarre i valori in modo sicuro
Perché i metodi restituiscono Optional?
Se la collezione è vuota (ad esempio cerchi il massimo in una lista vuota), viene restituito un Optional vuoto. Se chiami subito getAsInt()/get(), verrà lanciata un’eccezione.
Modalità corrette:
- Usare orElse(valore predefinito)
- Usare ifPresent(...) per eseguire azioni solo quando è presente un valore
- Usare orElseThrow(...) per lanciare esplicitamente un’eccezione
Esempio:
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
System.out.println("Prezzo medio: " + avg.orElse(0.0));
Quando usare gli stream primitivi e quando i Collectors
- Se devi solo calcolare somma/media/minimo/massimo su un campo numerico — usa mapToInt e il relativo metodo (sum, average, min, max).
- Se vuoi aggregare oggetti (per esempio trovare l’oggetto con il campo massimo), usa i collector maxBy/minBy.
- Se devi raggruppare e calcolare aggregazioni per gruppi — usa la combinazione di groupingBy con i collector di aggregazione.
7. Errori tipici nell’uso dei metodi di aggregazione
Errore n. 1: Optional non gestito. Spesso i principianti dimenticano che min, max, average restituiscono un Optional. Di conseguenza, tentando di ottenere un valore da un contenitore vuoto si ottiene una NoSuchElementException. Usa sempre orElse, orElseThrow o ifPresent.
Errore n. 2: uso di uno stream normale invece di mapToInt/mapToDouble. Se scrivi stream().sum(), il compilatore dirà: «Non esiste un tale metodo!». Per somma, media, minimo e massimo servono gli stream primitivi.
Errore n. 3: incongruenza di tipi nel confronto. Quando cerchi massimo/minimo tra oggetti, usa il comparatore corretto: Comparator.comparingInt(Employee::getSalary). Altrimenti potresti ottenere un errore di compilazione o un risultato scorretto.
Errore n. 4: confronto di oggetti con null. Se nella collezione possono esserci elementi null, l’aggregazione può generare NullPointerException. Meglio filtrare in anticipo tali elementi o usare Comparator.nullsLast(...)/nullsFirst(...).
Errore n. 5: usare sum/average/min/max con collezioni vuote senza considerare il risultato. Se la collezione è vuota, il metodo sum() restituirà 0, mentre average()/min()/max() — un Optional vuoto. Non dimenticare di gestire questo caso.
GO TO FULL VERSION