CodeGym /Corsi /JAVA 25 SELF /Analisi degli errori tipici nell'uso degli stream

Analisi degli errori tipici nell'uso degli stream

JAVA 25 SELF
Livello 32 , Lezione 4
Disponibile

1. Errore: riutilizzo dello stream

Uno stream (Stream) in Java è uno strumento monouso. Non appena eseguite un’operazione terminale (per esempio, collect(), forEach(), count()), lo stream si chiude e non è più utilizzabile.

Esempio di errore

Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); // Tutto bene
stream.forEach(System.out::println); // Genererà IllegalStateException!

Output:

a
b
c
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

Come evitarlo

Se occorre eseguire più operazioni, create un nuovo stream ogni volta:

List<String> list = List.of("a", "b", "c");
list.stream().forEach(System.out::println); // OK
list.stream().count(); // OK

Trucco: Uno stream è come un bicchiere monouso della mensa: usalo e poi gettalo; prendine uno nuovo per la bevanda successiva.

2. Errore: modificare la collezione durante lo stream

Se modificate la collezione di origine durante l’iterazione tramite stream, otterrete una ConcurrentModificationException. È un problema comune durante il filtraggio e la rimozione di elementi.

Esempio di errore

List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
names.stream().forEach(name -> {
    if (name.startsWith("A")) {
        names.remove(name); // BOOM!
    }
});

Risultato:

Exception in thread "main" java.util.ConcurrentModificationException

Modo corretto

Usate metodi che non modificano la collezione durante l’iterazione, oppure raccogliete il risultato in una nuova collezione:

names.removeIf(name -> name.startsWith("A")); // OK, removeIf è sicuro

// Oppure con lo Stream API:
List<String> filtered = names.stream()
    .filter(name -> !name.startsWith("A"))
    .collect(Collectors.toList());

Ricorda: Gli stream amano la purezza — non toccate la collezione di origine mentre la stanno «consumando».

3. Errore: limit dimenticato con gli stream infiniti

I metodi Stream.iterate e Stream.generate possono creare stream infiniti. Se dimenticate di limitarli con il metodo limit, potete rimanere bloccati per sempre (o fino a OutOfMemoryError).

Esempio di errore

Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
infinite.forEach(System.out::println); // Il programma non terminerà mai!

Modo corretto

Limitate lo stream con il metodo limit:

Stream<Integer> finite = Stream.iterate(0, n -> n + 1).limit(10);
finite.forEach(System.out::println); // Stamperà i numeri da 0 a 9

Consiglio: Gli stream infiniti ricordano una serie senza episodio finale: qualcuno deve fermarla, ed è meglio che siate voi!

4. Errore: gestione errata di null

Lo Stream API non ama il null. Se nella collezione c’è un null, molte operazioni possono lanciare NullPointerException, soprattutto se invocate metodi sugli elementi.

Esempio di errore

List<String> names = Arrays.asList("Alice", null, "Bob");
names.stream()
    .map(String::toUpperCase) // BOOM! su null
    .forEach(System.out::println);

Risultato:

Exception in thread "main" java.lang.NullPointerException

Modo corretto

Filtrate in anticipo i valori null:

names.stream()
    .filter(Objects::nonNull)
    .map(String::toUpperCase)
    .forEach(System.out::println);

Lavorare con Optional

Se avete uno Stream<Optional<T>>, usate flatMap(Optional::stream) (Java 9+) per «espanderli»:

List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty(), Optional.of("B"));
optionals.stream()
    .flatMap(Optional::stream)
    .forEach(System.out::println); // Stamperà solo "A" e "B"

Consiglio: Cercate di non aggiungere null alle collezioni. Se proprio non se ne può fare a meno, filtrateli all’inizio.

5. Errore: perdita dell’ordine degli elementi

Alcune operazioni (per esempio, flatMap, concat) possono influire sull’ordine degli elementi, soprattutto se lavorate con stream paralleli o unificate collezioni diverse.

Esempio di errore

List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = List.of(4, 5, 6);
Stream<Integer> combined = Stream.concat(list1.stream(), list2.stream());
combined.forEach(System.out::print); // 123456 — l'ordine è preservato

// Ma usando stream paralleli:
Stream<Integer> par = Stream.concat(list1.parallelStream(), list2.parallelStream());
par.forEach(System.out::print); // L'ordine può essere inaspettato!

Modo corretto

Se l’ordine è importante, evitate gli stream paralleli oppure usate metodi che garantiscono l’ordine (forEachOrdered):

par.forEachOrdered(System.out::print);

Fatto: Lo Stream API per impostazione predefinita preserva l’ordine, a meno che non siate voi a «romperlo».

6. Errore: unione inefficiente di collezioni

Per le operazioni sugli insiemi è meglio usare Set, non List, per evitare duplicati e migliorare le prestazioni. Inoltre è inefficiente scrivere cicli annidati dove si può usare flatMap.

Esempio di errore

List<String> list1 = List.of("a", "b", "c");
List<String> list2 = List.of("b", "c", "d");

// Inefficiente:
List<String> union = new ArrayList<>(list1);
for (String s : list2) {
    if (!union.contains(s)) { // contains su List — O(n)
        union.add(s);
    }
}

// Efficiente con Set e Stream API:
Set<String> unionSet = Stream.concat(list1.stream(), list2.stream())
    .collect(Collectors.toSet());

Oppure per la differenza:

// Inefficiente:
List<String> diff = new ArrayList<>(list1);
diff.removeAll(list2); // OK, ma se lo faceste via stream e list.contains sarebbe lento

// Efficiente:
Set<String> set2 = new HashSet<>(list2);
List<String> difference = list1.stream()
    .filter(s -> !set2.contains(s))
    .collect(Collectors.toList());

Consiglio: Per lavorare con elementi unici usate sempre Set. Per collezioni grandi evitate cicli annidati: preferite stream + flatMap.

7. Errore: operazioni terminali ripetute

A volte si desidera «sdoppiare» uno stream, ad esempio raccogliere gli elementi in una lista e in parallelo contarli. Ma lo stream è «monouso» e la seconda operazione provocherà un errore.

Esempio di errore

Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
long count = stream.count(); // BOOM! IllegalStateException

Modo corretto

Se dovete usare i dati più volte, prima raccoglieteli in una collezione:

List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());
long count = list.size();

Consiglio: Se volete fare due cose con gli stessi dati, prima raccogliete in una collezione.

8. Errore: close() dimenticato per le risorse

Se usate stream collegati a risorse (per esempio, Files.lines(Path)), non dimenticate di chiuderli dopo l’uso per evitare perdite.

Esempio di errore

Stream<String> lines = Files.lines(Path.of("file.txt"));
lines.forEach(System.out::println); // OK, ma la risorsa non è chiusa!

Modo corretto

Usate try-with-resources:

try (Stream<String> lines = Files.lines(Path.of("file.txt"))) {
    lines.forEach(System.out::println);
}

Consiglio: Gli stream su file sono come un bollitore: fallo bollire e poi spegnilo!

9. Errore: gestione scorretta di Optional e null in flatMap

Lavorando con collezioni del tipo List<Optional<T>>, molti usano map(Optional::get), il che porta a eccezioni se l’Optional è vuoto.

Esempio di errore

List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty());
optionals.stream()
    .map(Optional::get) // BOOM! NoSuchElementException
    .forEach(System.out::println);

Modo corretto

Usate flatMap(Optional::stream) (Java 9+):

optionals.stream()
    .flatMap(Optional::stream)
    .forEach(System.out::println); // Solo "A"

Consiglio: Optional non è solo un involucro, ma uno strumento per lavorare in sicurezza con l’assenza di valore.

10. Errore: equals/hashCode implementati in modo errato

Le operazioni sugli insiemi (Set, distinct, unione, intersezione) dipendono da una corretta implementazione dei metodi equals e hashCode.

Esempio di errore

class Student {
    String name;
    int age;
    // Mancano equals e hashCode!
}

Set<Student> students = new HashSet<>();
students.add(new Student("Alice", 20));
students.add(new Student("Alice", 20));
System.out.println(students.size()); // 2, anche se dovrebbero essere considerati uguali!

Modo corretto

Sovrascrivete equals e hashCode per le vostre classi se intendete usarle nelle collezioni Set, Map o per operazioni di distinct.

11. Errore: uso dello stream dopo collect

Dopo un’operazione terminale (per esempio, collect) lo stream si chiude. Qualsiasi tentativo di continuare a lavorare con lo stesso stream porterà a un errore.

Esempio di errore

Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
stream.filter(s -> s.startsWith("a")).forEach(System.out::println); // Exception!

Modo corretto

Create un nuovo stream dalla collezione:

list.stream().filter(s -> s.startsWith("a")).forEach(System.out::println);

12. Errore: uso scorretto di parallelStream

Molti pensano che parallelStream() acceleri sempre l’elaborazione. In realtà, per collezioni piccole o operazioni semplici il parallelismo può rallentare a causa dell’overhead di gestione dei thread.

Esempio di errore

List<Integer> numbers = IntStream.range(0, 10).boxed().collect(Collectors.toList());
numbers.parallelStream().forEach(System.out::println); // Può essere più lento del normale stream()

Modo corretto

Usate parallelStream solo per collezioni davvero grandi o calcoli pesanti, soprattutto se le operazioni sono pure (senza effetti collaterali) e associative.

13. Errore: operatore terminale dimenticato

Le operazioni intermedie (map, filter, flatMap ecc.) non vengono eseguite finché non viene invocato un operatore terminale (collect, forEach, count ecc.).

Esempio di errore

Stream<String> stream = Stream.of("a", "b", "c").map(String::toUpperCase);
// Non succederà nulla!

Modo corretto

Richiamate sempre un operatore terminale:

stream.forEach(System.out::println);
1
Sondaggio/quiz
Unioni e proiezioni, livello 32, lezione 4
Non disponibile
Unioni e proiezioni
Stream API: unioni e proiezioni
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION