Evaluación Perezosa (Lazy Evaluation) de la Streams API
Las operaciones intermedias (filter, map) no procesan ningún dato cuando se declaran; solo construyen un plan. El procesamiento solo arranca al invocar la operación terminal (toList, collect).
En una línea lista.stream().filter(x -> x > 10).map(String::valueOf).toList();
Optional<String> primero = Stream.of("ana", "bea", "carlos", "dani")
.filter(n -> {
System.out.println("filter " + n);
return n.length() > 3;
})
.map(n -> {
System.out.println("map " + n);
return n.toUpperCase();
})
.findFirst();
// filter ana
// filter bea
// filter carlos
// map carlos <- "dani" nunca se procesa: findFirst ya tiene respuesta
// Fuente infinita: solo funciona gracias a la pereza
List<Integer> potencias = Stream.iterate(1, x -> x * 2)
.limit(10)
.toList();
Cómo funciona por dentro
Un stream se compone de una fuente, cero o más operaciones intermedias (filter, map, sorted) y una operación terminal (toList, count, findFirst). Las intermedias no hacen nada por sí solas: construyen una receta. Solo cuando invocas la terminal empieza a fluir el primer elemento.
El procesamiento es vertical: cada elemento recorre todo el pipeline antes de que entre el siguiente, en lugar de aplicar filter a toda la lista y después map a todo el resultado. Eso permite el cortocircuito: con findFirst, anyMatch o limit, el stream deja de pedir elementos en cuanto tiene la respuesta, y funciona incluso con fuentes infinitas.
Las excepciones son las operaciones con estado como sorted o distinct, que necesitan ver todos los elementos antes de emitir el primero. Un stream además solo se puede consumir una vez; reutilizarlo lanza IllegalStateException.
.findFirst(), el Stream se detiene en cuanto encuentra el primer elemento sin procesar el resto de la lista.
.filter() crea una lista intermedia en memoria; el Stream procesa elemento a elemento en una sola pasada vertical.
toList() inmutable disponible tanto en Java 17 como en 21.
Se conecta con Interfaces FuncionalesPipelinesOptimización