CodeGym /Cursos /JAVA 25 SELF /Análisis de errores típicos al trabajar con streams

Análisis de errores típicos al trabajar con streams

JAVA 25 SELF
Nivel 32 , Lección 4
Disponible

1. Error: reutilización del stream

Un stream (Stream) en Java es una herramienta de un solo uso. En cuanto ejecutas una operación terminal (por ejemplo, collect(), forEach(), count()), el stream se cierra y deja de ser apto para usarse de nuevo.

Ejemplo de error

Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); // Todo bien
stream.forEach(System.out::println); // ¡Lanzará IllegalStateException!

Salida:

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

Cómo evitarlo

Si necesitas realizar varias operaciones, crea un nuevo stream cada vez:

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

Truco: Un stream es como un vaso desechable del comedor: lo usas y lo tiras; coge uno nuevo para la siguiente bebida.

2. Error: modificar la colección durante el stream

Si modificas la colección de origen mientras la recorres con un stream, obtendrás ConcurrentModificationException. Es un dolor frecuente al filtrar y eliminar elementos.

Ejemplo de error

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

Resultado:

Exception in thread "main" java.util.ConcurrentModificationException

Cómo hacerlo bien

Usa métodos que no modifiquen la colección durante la iteración, o recopila el resultado en una colección nueva:

names.removeIf(name -> name.startsWith("A")); // OK, removeIf es seguro

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

Recuerda: A los streams les gusta la pureza: no toques la colección de origen mientras la «consumen» los streams.

3. Error: olvidar limit en streams infinitos

Los métodos Stream.iterate y Stream.generate pueden crear streams infinitos. Si olvidas limitar su tamaño con limit, puedes quedarte colgado para siempre (o hasta un OutOfMemoryError).

Ejemplo de error

Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
infinite.forEach(System.out::println); // ¡El programa nunca terminará!

Cómo hacerlo bien

Limita el stream con limit:

Stream<Integer> finite = Stream.iterate(0, n -> n + 1).limit(10);
finite.forEach(System.out::println); // Imprimirá los números del 0 al 9

Consejo: Los streams infinitos se parecen a una serie sin capítulo final: alguien tiene que parar, y mejor que seas tú.

4. Error: manejo incorrecto de null

La API de Stream no se lleva bien con null. Si en la colección aparece null, muchas operaciones pueden lanzar NullPointerException, especialmente si llamas a métodos sobre los elementos.

Ejemplo de error

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

Resultado:

Exception in thread "main" java.lang.NullPointerException

Cómo hacerlo bien

Filtra los valores null de antemano:

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

Trabajo con Optional

Si tienes un Stream<Optional<T>>, usa flatMap(Optional::stream) (Java 9+) para «desenvolverlos»:

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

Consejo: Procura no añadir null a las colecciones. Si no hay más remedio, filtéralos al principio.

5. Error: pérdida del orden de los elementos

Algunas operaciones (por ejemplo, flatMap, concat) pueden afectar al orden de los elementos, especialmente si trabajas con streams paralelos o si unes distintas colecciones.

Ejemplo de error

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 — el orden se mantiene

// Pero si usas streams paralelos:
Stream<Integer> par = Stream.concat(list1.parallelStream(), list2.parallelStream());
par.forEach(System.out::print); // ¡El orden puede ser inesperado!

Cómo hacerlo bien

Si el orden importa, evita los streams paralelos o usa métodos que lo garanticen (forEachOrdered):

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

Hecho: La API de Stream conserva el orden por defecto, salvo que tú mismo lo hayas «roto».

6. Error: unión ineficiente de colecciones

Para operaciones sobre conjuntos es mejor usar Set en lugar de List, para evitar duplicados y mejorar el rendimiento. También es ineficiente escribir bucles anidados cuando puedes usar flatMap.

Ejemplo de error

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

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

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

O para la diferencia:

// Ineficiente:
List<String> diff = new ArrayList<>(list1);
diff.removeAll(list2); // OK, pero si lo hicieras con stream y list.contains — sería lento

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

Consejo: Para trabajar con elementos únicos, usa siempre Set. Para colecciones grandes, evita bucles anidados; prioriza stream + flatMap.

7. Error: reutilizar operaciones terminales

A veces apetece «dividir» un stream en dos ramas, por ejemplo, recopilar los elementos en una lista y a la vez contarlos. Pero un stream es de un solo uso y la segunda operación provocará un error.

Ejemplo de error

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

Cómo hacerlo bien

Si necesitas usar el stream varias veces, primero recopílalo en una colección:

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

Consejo: Si quieres ambas cosas, primero recopila en una colección.

8. Error: olvidar close() en recursos

Si usas streams asociados a recursos (por ejemplo, Files.lines(Path)), no olvides cerrarlos tras su uso para evitar fugas.

Ejemplo de error

Stream<String> lines = Files.lines(Path.of("file.txt"));
lines.forEach(System.out::println); // OK, pero el recurso no está cerrado!

Cómo hacerlo bien

Usa try-with-resources:

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

Consejo: Los streams de archivos son como una tetera: caliéntala y apágala.

9. Error: manejo incorrecto de Optional y null en flatMap

Al trabajar con colecciones del tipo List<Optional<T>> muchos usan map(Optional::get), lo que provoca excepciones si dentro de Optional no hay valor.

Ejemplo de error

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

Cómo hacerlo bien

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

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

Consejo: Optional no es solo un envoltorio, sino una herramienta para trabajar de forma segura con la ausencia de valor.

10. Error: equals/hashCode mal implementados

Las operaciones sobre conjuntos (Set, distinct, unión, intersección) dependen de una implementación correcta de los métodos equals y hashCode.

Ejemplo de error

class Student {
    String name;
    int age;
    // ¡Sin equals ni hashCode!
}

Set<Student> students = new HashSet<>();
students.add(new Student("Alice", 20));
students.add(new Student("Alice", 20));
System.out.println(students.size()); // 2, aunque deberían considerarse iguales.

Cómo hacerlo bien

Sobrescribe equals y hashCode en tus clases si planeas usarlas en colecciones Set, Map o para operaciones distinct.

11. Error: usar el stream después de collect

Tras una operación terminal (por ejemplo, collect) el stream se cierra. Cualquier intento de continuar trabajando con ese mismo stream provocará un error.

Ejemplo de error

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); // ¡Excepción!

Cómo hacerlo bien

Crea un nuevo stream a partir de la colección:

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

12. Error: uso incorrecto de parallelStream

Muchos piensan que parallelStream() siempre acelera el procesamiento. En realidad, para colecciones pequeñas u operaciones simples, el paralelismo puede incluso ralentizar la ejecución debido al overhead de gestión de hilos.

Ejemplo de error

List<Integer> numbers = IntStream.range(0, 10).boxed().collect(Collectors.toList());
numbers.parallelStream().forEach(System.out::println); // Puede ser más lento que un stream() normal

Cómo hacerlo bien

Usa parallelStream solo para colecciones realmente grandes o cálculos costosos, especialmente si las operaciones son puras (sin efectos secundarios) y asociativas.

13. Error: operador terminal olvidado

Las operaciones intermedias (map, filter, flatMap, etc.) no se ejecutan hasta que se invoca un operador terminal (collect, forEach, count, etc.).

Ejemplo de error

Stream<String> stream = Stream.of("a", "b", "c").map(String::toUpperCase);
// ¡No ocurrirá nada!

Cómo hacerlo bien

Asegúrate de invocar un operador terminal:

stream.forEach(System.out::println);
1
Tarea
JAVA 25 SELF, nivel 32, lección 4
Bloqueada
Limpieza peligrosa de la base de datos con posibles registros "vacíos" 😼
Limpieza peligrosa de la base de datos con posibles registros "vacíos" 😼
1
Tarea
JAVA 25 SELF, nivel 32, lección 4
Bloqueada
Pergamino mágico de un solo uso de hechizos 📜
Pergamino mágico de un solo uso de hechizos 📜
1
Cuestionario/control
Uniones y proyecciones, nivel 32, lección 4
No disponible
Uniones y proyecciones
Stream API: uniones y proyecciones
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION