Command Palette

Search for a command to run...

ES·EN

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.

Puntos clave

  • Los streams son lazy — ninguna computación ocurre hasta que se llama a una operación terminal. Esto habilita la optimización de cortocircuito.
  • Los streams paralelos usan ForkJoinPool.commonPool(). No los uses para operaciones I/O-bound o cuando la lambda captura estado mutable compartido.
  • Collectors.groupingBy() es una reducción poderosa — agrupá elementos por un clasificador y aplicá un collector downstream a cada grupo.

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);