Nivel 2 · 20 min
Streams y Funcional
La Stream API de Java (Java 8+) permite el procesamiento de datos declarativo en estilo pipeline. Entender la evaluación lazy, cuándo ocurre realmente la computación y las trampas de los streams paralelos es esencial para escribir código de procesamiento de colecciones correcto y eficiente.
Pipeline de Stream: Fuente → Intermedio → Terminal
Un pipeline de stream tiene tres partes: 1) Fuente (Collection.stream(), Stream.of(), Files.lines()), 2) Operaciones intermedias (filter, map, flatMap, sorted, distinct, limit, skip) — son lazy y retornan un nuevo Stream, 3) Operaciones terminales (collect, forEach, count, reduce, findFirst, anyMatch) — disparan la ejecución del pipeline completo. No ocurre ninguna computación hasta que se invoca una operación terminal.
Evaluación lazy y operaciones de cortocircuito
Operaciones intermedias de cortocircuito: limit(n) se detiene después de n elementos, takeWhile (Java 9) se detiene cuando el predicado falla. Operaciones terminales de cortocircuito: findFirst(), findAny(), anyMatch(), allMatch(), noneMatch(). El pipeline procesa un elemento a la vez a través de todas las etapas, no etapa por etapa. Esto habilita la salida temprana y evita materializar colecciones intermedias.
Streams paralelos y la Collector API
Los streams paralelos dividen la fuente, procesan chunks concurrentemente en ForkJoinPool.commonPool() y fusionan los resultados. Requisitos para paralelización segura: 1) Operaciones no interferentes (no modifiques la fuente durante el procesamiento), 2) Lambdas sin estado (sin estado mutable compartido), 3) Operaciones asociativas para reducción. Los streams paralelos tienen overhead de división y fusión — solo ganan en conjuntos de datos grandes ('>' 10.000 elementos) con operaciones intensivas en CPU.
Code example
// Pipeline lazy — ninguna computación hasta collect()
List'<'String'>' result = employees.stream()
.filter(e -'>' e.getSalary() '>' 50_000)
.map(Employee::getName)
.sorted()
.collect(Collectors.toList());
// Cortocircuito: se detiene en el primer match
Optional'<'Employee'>' first = employees.stream()
.filter(e -'>' e.getDept().equals("Ingeniería"))
.findFirst(); // pipeline se detiene al encontrar el primero
// Collectors.groupingBy con downstream
Map'<'String, Long'>' countByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDept,
Collectors.counting()));
// Referencias a métodos
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);