CodeGym /Cursos /JAVA 25 SELF /Evaluación perezosa (lazy evaluation) en Stream API

Evaluación perezosa (lazy evaluation) en Stream API

JAVA 25 SELF
Nivel 33 , Lección 1
Disponible

1. ¿Qué es la evaluación perezosa?

La evaluación perezosa, o «cálculos perezosos» (lazy evaluation), es el principio por el cual las operaciones sobre los datos se posponen hasta que el resultado realmente hace falta. En el contexto de Stream API esto significa: si escribes una cadena de transformaciones sobre una colección, Java no las ejecuta enseguida: espera hasta que se invoque una operación terminal. Solo entonces se evalúa toda la cadena.

¿Para qué sirve? Primero, ahorra recursos: los elementos que al final no se necesitan simplemente no se procesan. Segundo, mejora el rendimiento: se pueden construir cadenas largas sin crear múltiples colecciones intermedias. Por último, hay «cálculos cortos»: en cuanto se encuentra el primer elemento adecuado, se detiene el procesamiento.

Analogía: imagina a un camarero perezoso. Dices: «Tráeme la carta, luego un café y luego un pastel». Él asiente, pero no hace nada… hasta que añades: «Y ahora tráelo de verdad». Entonces va a atender el pedido, y puede que traiga solo el café si ya no quedan pasteles. Así funcionan también los streams con evaluación perezosa.

2. Operaciones intermedias y terminales

Intermedias (intermediate):

  • filter
  • map
  • sorted
  • distinct
  • peek (para depuración)
  • y otras

Las operaciones intermedias devuelven un nuevo Stream, pero no inician los cálculos. Solo «construyen el plan» de procesamiento.

Terminales (terminal):

  • collect
  • forEach
  • reduce
  • count
  • findFirst, findAny
  • anyMatch, allMatch, noneMatch
  • y otras

Solo una operación terminal inicia la ejecución de toda la cadena.

Ejemplo: no pasa nada sin una operación terminal

List<String> names = List.of("Alicia", "Bob", "Ivan");

names.stream()
     .filter(name -> {
         System.out.println("Filtrando " + name);
         return name.startsWith("A");
     });
// No habrá ningún mensaje. El código de arriba solo "construye" la cadena.

Ahora añadimos una operación terminal:

names.stream()
     .filter(name -> {
         System.out.println("Filtrando " + name);
         return name.startsWith("A");
     })
     .forEach(System.out::println);
// ¡Ahora veremos la salida en la consola!

Resultado:

Filtrando Alicia
Filtrando Bob
Filtrando Ivan
Alicia

3. Ventajas de la evaluación perezosa

Ahorro de recursos

La evaluación perezosa permite no gastar tiempo ni memoria en elementos que no hacen falta. Por ejemplo, si buscas el primer objeto adecuado, el procesamiento se detendrá en la primera coincidencia.

List<String> names = List.of("Alicia", "Bob", "Ivan", "Anna");

String firstA = names.stream()
    .filter(name -> {
        System.out.println("Comprobando: " + name);
        return name.startsWith("A");
    })
    .findFirst()
    .orElse("No encontrado");

System.out.println("Resultado: " + firstA);

Salida:

Comprobando: Alicia
Resultado: Alicia

Fíjate: ¡los demás elementos ni siquiera se comprueban!

Cadenas largas sin colecciones intermedias

Se pueden combinar muchas operaciones (filter, map, sorted, etc.) sin crear colecciones en cada paso.

List<String> names = List.of("Alicia", "Bob", "Ivan", "Anna");

List<String> result = names.stream()
    .filter(name -> name.length() > 3)
    .map(String::toUpperCase)
    .sorted()
    .toList(); // Java 16+, antes — .collect(Collectors.toList())

Cálculos cortos

Si basta con saber «si hay algún elemento adecuado», los demás no se comprobarán:

boolean hasLongName = names.stream()
    .anyMatch(name -> {
        System.out.println("Comprobando: " + name);
        return name.length() > 10;
    });

// Si el primer elemento es largo — ¡los demás no se comprobarán!

4. Ejemplos: cómo funciona la evaluación perezosa

Ejemplo 1: no pasa nada sin una operación terminal

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

numbers.stream()
    .filter(n -> {
        System.out.println("Filtrando " + n);
        return n % 2 == 0;
    });
// ¡Sin salida!

Ejemplo 2: cadena con operación terminal

numbers.stream()
    .filter(n -> {
        System.out.println("Filtrando " + n);
        return n % 2 == 0;
    })
    .map(n -> {
        System.out.println("Multiplicando " + n);
        return n * 10;
    })
    .forEach(System.out::println);

Salida:

Filtrando 1
Filtrando 2
Multiplicando 2
20
Filtrando 3
Filtrando 4
Multiplicando 4
40
Filtrando 5

Importante: las operaciones se ejecutan elemento a elemento: primero filter, luego map, después forEach, para cada elemento por turno. No son dos pasadas separadas de «primero filtrar todo, luego transformar todo».

Ejemplo 3: uso de peek para depuración

numbers.stream()
    .filter(n -> n % 2 == 0)
    .peek(n -> System.out.println("Pasó el filtro: " + n))
    .map(n -> n * 10)
    .peek(n -> System.out.println("Después de map: " + n))
    .forEach(System.out::println);

5. Matices útiles

No uses streams con efectos secundarios

La pereza puede jugar una mala pasada si esperas ejecución inmediata. Acciones con efectos secundarios dentro de map, filter o peek (escritura a archivo, modificación de estado externo) pueden ejecutarse en orden distinto, no para todos los elementos o no ejecutarse en absoluto sin una operación terminal.

Filtra lo antes posible

Coloca filter cerca del inicio de la cadena para descartar antes los elementos sobrantes y reducir el trabajo posterior.

¿Solo necesitas el primer resultado? Usa los terminales correspondientes

Si necesitas el primer elemento adecuado, llama a findFirst o findAny. Permitirá que el stream se detenga en cuanto encuentre el resultado.

Los streams no son para modificar la colección original

Los streams no están pensados para añadir/eliminar elementos de la colección de origen. Para modificar la estructura de una colección, usa otros mecanismos.

Visualización del funcionamiento de streams perezosos

List<String> words = List.of("cat", "dog", "elephant", "fox", "giraffe");

words.stream()
    .filter(w -> w.length() > 3)
    .map(String::toUpperCase)
    .forEach(System.out::println);

Cómo sucede:

Etapa cat dog elephant fox giraffe
filter
map
ELEPHANT GIRAFFE
forEach
imprimir imprimir

Tabla: comparación de los enfoques eager y lazy

Enfoque ¿Cuándo se ejecuta el procesamiento? Uso de memoria Rendimiento
Eager (ansioso) Inmediatamente al invocar Puede ser alto A veces lento
Lazy (perezoso) Solo cuando es necesario Mínimo Normalmente más rápido

Enfoque ansioso — por ejemplo, cuando organizas manualmente varias pasadas sobre la colección creando listas intermedias.
Enfoque perezoso — los streams: no se hace nada hasta que se necesita el resultado.

6. Errores típicos al trabajar con streams perezosos

Error n.º 1: esperar un resultado inmediato. Los principiantes piensan que las llamadas a filter o map se ejecutan enseguida. Pero sin una terminal (por ejemplo, collect, forEach) no pasará nada — de ahí «no funciona la depuración», «no se imprime nada».

Error n.º 2: efectos secundarios en operaciones intermedias. Escribir a archivo o cambiar variables externas dentro de map/filter/peek es una mala práctica. Por la pereza y optimizaciones, tales acciones pueden ejecutarse de forma incompleta, en un orden inesperado o no ejecutarse en absoluto.

Error n.º 3: olvidar llamar a una operación terminal. Se escribe una cadena de streams pero no se cierra con collect, forEach, etc. Resultado: «silencio».

Error n.º 4: esperar que todos los elementos se procesen. Operaciones como findFirst o anyMatch interrumpen la tubería en el primer resultado. Los demás elementos no se procesan — de ahí la sorpresa de «¿por qué mi println no se ejecutó para todos?».

Error n.º 5: usar streams para modificar la colección original. Los streams no están diseñados para modificar colecciones de origen (añadir/eliminar elementos). Usa métodos especializados de las colecciones o iteradores.

1
Tarea
JAVA 25 SELF, nivel 33, lección 1
Bloqueada
El gato perezoso y su cena: simulación sin resultado 😼
El gato perezoso y su cena: simulación sin resultado 😼
1
Tarea
JAVA 25 SELF, nivel 33, lección 1
Bloqueada
Búsqueda de un artefacto raro en una antigua cámara del tesoro 💎
Búsqueda de un artefacto raro en una antigua cámara del tesoro 💎
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION