1. Creiamo uno stream
Per usare la Stream API, devi prima ottenere uno stream da una collezione o da un array.
Esempi di creazione di uno stream
// Da una lista
List<String> names = List.of("Anna", "Boris", "Alex", "Alina");
Stream<String> stream = names.stream();
// Da un array
int[] numbers = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(numbers);
// Da valori singoli
Stream<String> letters = Stream.of("A", "B", "C");
In breve:
- list.stream() — per le collezioni
- Arrays.stream(array) — per gli array
- Stream.of(...) — per valori singoli
Esempio nel contesto della nostra applicazione
Supponiamo di avere una lista di utenti:
List<String> users = List.of("Ivan", "Anna", "Petr", "Alexey");
Stream<String> userStream = users.stream();
Operazioni intermedie e terminali
Punto importante: le operazioni nella Stream API si dividono in due tipi.
- Operazioni intermedie (ad esempio, filter, map, distinct) — descrivono le fasi di elaborazione. Restituiscono un nuovo stream, ma di per sé non avviano nulla.
- Operazioni terminali (ad esempio, collect, forEach, count) — avviano la pipeline e producono il risultato.
Lo stream è «pigro»: finché non viene chiamata un’operazione terminale, non avviene alcun calcolo. Ecco perché spesso concludiamo la catena con collect(...): è il punto in cui lo stream si trasforma di nuovo in una collezione o in un altro risultato.
2. Operazione filter: filtrare gli elementi per condizione
filter è un’operazione intermedia che lascia passare solo gli elementi che soddisfano una data condizione.
Firma
Stream<T> filter(Predicate<? super T> predicate);
Predicate è un’interfaccia funzionale che prende un elemento e restituisce true (tenere) oppure false (scartare).
Esempio: tenere solo i numeri pari
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // [2, 4, 6]
Cosa succede?
- n -> n % 2 == 0 — espressione lambda che verifica se il numero è divisibile per 2 senza resto.
- filter lascia solo i numeri pari.
Esempio: filtrare i nomi che iniziano con «A»
List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");
List<String> aNames = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
System.out.println(aNames); // [Anna, Alex, Alina]
Punto importante: filter non modifica la collezione: crea un nuovo stream che contiene solo gli elementi necessari.
3. Operazione map: trasformare un elemento in qualcos’altro
map è un’operazione di trasformazione. Prende ogni elemento dello stream, applica una funzione e restituisce un nuovo elemento.
Firma
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
Function è un’interfaccia che prende un elemento e restituisce qualcosa (anche di un altro tipo).
Esempio: ottenere le lunghezze delle stringhe
List<String> names = List.of("Anna", "Boris", "Alex");
List<Integer> nameLengths = names.stream()
.map(name -> name.length())
.collect(Collectors.toList());
System.out.println(nameLengths); // [4, 5, 4]
Cosa succede?
- map trasforma la stringa nella sua lunghezza (name -> name.length()).
- Il risultato è uno stream di numeri.
Esempio: portare le stringhe in maiuscolo
List<String> names = List.of("Anna", "Boris", "Alex");
List<String> upperNames = names.stream()
.map(name -> name.toUpperCase())
.collect(Collectors.toList());
System.out.println(upperNames); // [ANNA, BORIS, ALEX]
4. Operazione collect: raccogliere il risultato di nuovo in una collezione
collect è un’operazione terminale, cioè conclude lo stream e raccoglie il risultato in una collezione o in un altro contenitore.
Firma
<R, A> R collect(Collector<? super T, A, R> collector)
Niente paura per la firma spaventosa! Nel 99% dei casi userete i collector già pronti della classe Collectors.
Collectors è una classe di utilità con un insieme di «raccoglitori». Indica allo stream in quale forma raccogliere il risultato: lista, insieme, stringa, ecc.
Esempi:
- Collectors.toList() — in List
- Collectors.toSet() — in Set
- Collectors.joining(", ") — in una stringa separata da virgola
In altre parole, Collectors è come un set di scatole di forme diverse in cui impacchettate gli elementi dello stream.
Esempio: raccogliere il risultato in una List
List<String> filtered = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
Esempio: raccogliere il risultato in un Set
Set<String> uniqueNames = names.stream()
.map(String::toLowerCase)
.collect(Collectors.toSet());
Esempio: unire le stringhe con una virgola
String result = names.stream()
.collect(Collectors.joining(", "));
System.out.println(result); // Anna, Boris, Alex
5. Catena di operazioni: filtro + trasformazione + raccolta del risultato
Il punto di forza principale della Stream API è la possibilità di concatenare le operazioni l’una dopo l’altra.
Esempio: ottenere le lunghezze dei nomi che iniziano con «A»
List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");
List<Integer> aNameLengths = names.stream()
.filter(name -> name.startsWith("A"))
.map(String::length)
.collect(Collectors.toList());
System.out.println(aNameLengths); // [4, 4, 5]
Passo per passo:
- .stream() — creiamo uno stream dalla lista.
- .filter(name -> name.startsWith("A")) — lasciamo solo i nomi che iniziano per "A".
- .map(String::length) — trasformiamo ogni nome nella sua lunghezza.
- .collect(Collectors.toList()) — raccogliamo il risultato in una lista.
Codice imperativo equivalente
Ecco come apparirebbe lo stesso «alla vecchia maniera»:
List<Integer> result = new ArrayList<>();
for (String name : names) {
if (name.startsWith("A")) {
result.add(name.length());
}
}
Confronta: Stream API — una riga, si legge come «cosa facciamo», non «come lo facciamo».
6. Pratica: alcuni brevi esercizi
Alleniamoci! Tutti gli esempi si possono eseguire in un unico file — basta cambiare i dati.
Esercizio 1: lasciare solo i numeri dispari e elevarli al quadrato
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);
List<Integer> oddSquares = numbers.stream()
.filter(n -> n % 2 != 0)
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(oddSquares); // [1, 9, 25, 49]
Esercizio 2: da una lista di stringhe ottenere la lista delle loro prime lettere
List<String> names = List.of("Anna", "Boris", "Alex");
List<Character> initials = names.stream()
.map(name -> name.charAt(0))
.collect(Collectors.toList());
System.out.println(initials); // [A, B, A]
Esercizio 3: filtrare le stringhe con lunghezza > 3 e raccoglierle in un Set
List<String> words = List.of("cat", "dog", "elephant", "ant", "bear");
Set<String> longWords = words.stream()
.filter(word -> word.length() > 3)
.collect(Collectors.toSet());
System.out.println(longWords); // [bear, elephant]
7. Errori tipici lavorando con filter, map, collect
Errore n. 1: hai dimenticato collect — nessun risultato!
La Stream API è pigra come un gatto sul davanzale: finché non chiami un’operazione terminale (ad esempio, collect o forEach), non succederà nulla. Se scrivi solo users.stream().filter(...).map(...); — non verrà eseguita alcuna azione.
Errore n. 2: filter e map scambiati di posto
A volte i principianti applicano prima map e poi filter. Per esempio, names.stream().map(String::length).filter(len -> len > 3) — produce numeri, non stringhe. Se ti servono stringhe di lunghezza maggiore di 3, prima filtra e poi trasforma.
Errore n. 3: dimenticare l’immutabilità
Le operazioni della Stream API non modificano la collezione originale! Restituiscono un nuovo risultato. Dopo List<String> upper = names.stream().map(String::toUpperCase).collect(Collectors.toList()); — la collezione names resterà invariata.
Errore n. 4: tentare di usare una lista esterna mutabile
Meglio evitare così:
List<String> result = new ArrayList<>();
names.stream().filter(...).forEach(name -> result.add(name));
Meglio usare collect: è più sicuro e più conciso.
Errore n. 5: NullPointerException
Se nella collezione possono esserci elementi null, chiamare name.startsWith("A") su null causerà un errore. Aggiungi un filtro per null quando possibile:
.filter(name -> name != null && name.startsWith("A"))
GO TO FULL VERSION