Antes de Java 8, procesar una lista de objetos requería bucles for explícitos, variables auxiliares y lógica de control dispersa por el código. El resultado era verboso, difícil de leer y propenso a errores.
La API Stream, introducida en Java 8 en 2014, cambió completamente ese panorama. Con Streams puedes filtrar, transformar, agrupar y reducir colecciones de datos de forma declarativa — describes qué quieres hacer, no cómo recorrer la estructura.
En este artículo vas a entender qué son los Streams, cómo funcionan por dentro y cómo usar todas sus operaciones con ejemplos reales. Si ya tienes claros los conceptos de Programación Orientada a Objetos y los fundamentos de Java, este es el salto natural al Java moderno.
¿Qué es un Stream en Java?
Un Stream es una secuencia de elementos que soporta operaciones de procesamiento secuencial o paralelo. Hay tres cosas importantes que entender desde el principio:
- 📌 No almacena datos — un Stream no es una colección. Es una vista de los datos de otra fuente (lista, array, archivo) sobre la que aplicas transformaciones
- 📌 No modifica la fuente — las operaciones de Stream siempre producen un nuevo resultado sin tocar la colección original
- 📌 Evaluación lazy — las operaciones intermedias no se ejecutan hasta que una operación terminal las desencadena
// La diferencia entre el enfoque imperativo y el declarativo
// Datos de ejemplo
List<String> nombres = Arrays.asList(
"Ana García", "Luis Pérez", "María López", "Carlos Ruiz", "Elena Martín"
);
// ── ANTES DE JAVA 8 — enfoque imperativo (cómo hacerlo) ─────
List<String> resultado = new ArrayList<>();
for (String nombre : nombres) {
if (nombre.length() > 9) {
resultado.add(nombre.toUpperCase());
}
}
Collections.sort(resultado);
// Verboso, 4 líneas para algo conceptualmente simple
// ── CON STREAMS — enfoque declarativo (qué obtener) ─────────
List<String> resultado = nombres.stream()
.filter(n -> n.length() > 9)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
// Más legible: filtra, transforma, ordena, recogeLa anatomía de un Stream — fuente, intermedias y terminal
Todo pipeline de Stream tiene exactamente tres partes:
coleccion.stream() // 1. FUENTE — de dónde vienen los datos
.operacionIntermedia1() // 2. INTERMEDIAS — cero o más transformaciones
.operacionIntermedia2() // cada una devuelve un nuevo Stream
.operacionTerminal(); // 3. TERMINAL — produce el resultado final
// cierra el Stream (no se puede reusar)// Ejemplo real con las tres partes identificadas:
OptionalDouble precioMedio = productos.stream() // FUENTE
.filter(p -> p.getCategoria().equals("tech")) // INTERMEDIA: filtra
.map(Producto::getPrecio) // INTERMEDIA: transforma
.mapToDouble(Double::doubleValue) // INTERMEDIA: convierte
.average(); // TERMINAL: calculaCómo crear un Stream — las fuentes
import java.util.stream.*;
import java.util.*;
import java.io.*;
import java.nio.file.*;
// ── Desde una colección (la más común) ──────────────────────
List<String> lista = Arrays.asList("a", "b", "c");
Stream<String> streamLista = lista.stream();
Set<Integer> conjunto = Set.of(1, 2, 3, 4, 5);
Stream<Integer> streamSet = conjunto.stream();
// ── Desde un array ───────────────────────────────────────────
String[] array = {"Java", "Python", "JavaScript"};
Stream<String> streamArray = Arrays.stream(array);
// ── Desde valores directos ───────────────────────────────────
Stream<String> streamOf = Stream.of("uno", "dos", "tres");
// ── Stream vacío ─────────────────────────────────────────────
Stream<String> vacio = Stream.empty();
// ── Streams numéricos especializados ────────────────────────
IntStream numeros = IntStream.range(1, 11); // 1 al 10
IntStream numerosI = IntStream.rangeClosed(1, 10); // 1 al 10 (inclusive)
LongStream largos = LongStream.of(100L, 200L, 300L);
DoubleStream decimales = DoubleStream.of(1.1, 2.2, 3.3);
// ── Streams infinitos ────────────────────────────────────────
Stream<Integer> infinito = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6, 8...
Stream<Double> aleatorio = Stream.generate(Math::random); // valores random
// Siempre limita los streams infinitos antes de operar
infinito.limit(5).forEach(System.out::println); // 0 2 4 6 8
// ── Desde un archivo ─────────────────────────────────────────
try (Stream<String> lineas = Files.lines(Path.of("datos.txt"))) {
lineas.filter(l -> !l.isBlank())
.forEach(System.out::println);
} // try-with-resources cierra el Stream automáticamente
// ── Desde un Map ─────────────────────────────────────────────
Map<String, Integer> mapa = Map.of("Ana", 28, "Luis", 35);
mapa.entrySet().stream()
.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));Operaciones intermedias — las transformaciones
Las operaciones intermedias devuelven siempre un nuevo Stream. Son lazy: no se ejecutan hasta que una operación terminal las desencadene.
filter() — filtrar elementos
// filter(Predicate<T>) — mantiene solo los elementos que cumplen la condición
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Números pares
List<Integer> pares = numeros.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// [2, 4, 6, 8, 10]
// Múltiples filtros encadenados
List<Producto> resultado = productos.stream()
.filter(p -> p.isActivo())
.filter(p -> p.getPrecio() < 100)
.filter(p -> p.getStock() > 0)
.collect(Collectors.toList());
// Equivalente con &&
List<Producto> resultado2 = productos.stream()
.filter(p -> p.isActivo() && p.getPrecio() < 100 && p.getStock() > 0)
.collect(Collectors.toList());map() — transformar elementos
// map(Function<T, R>) — transforma cada elemento de tipo T a tipo R
List<String> nombres = Arrays.asList("ana", "luis", "maría");
// String → String (transformación del mismo tipo)
List<String> mayusculas = nombres.stream()
.map(String::toUpperCase) // referencia a método
.collect(Collectors.toList());
// ["ANA", "LUIS", "MARÍA"]
// String → Integer (cambio de tipo)
List<Integer> longitudes = nombres.stream()
.map(String::length)
.collect(Collectors.toList());
// [3, 4, 5]
// Objeto → campo del objeto (extracción de propiedad)
List<String> nombresProductos = productos.stream()
.map(Producto::getNombre)
.collect(Collectors.toList());
// Objeto → objeto transformado (DTO/Proyección)
List<ProductoDTO> dtos = productos.stream()
.map(p -> new ProductoDTO(p.getId(), p.getNombre(), p.getPrecio()))
.collect(Collectors.toList());mapToInt / mapToLong / mapToDouble — streams numéricos
// Los streams numéricos especializados tienen operaciones adicionales
// como sum(), average(), min(), max(), count() sin boxeo/unboxeo
// En lugar de map() + reduce() para sumas:
double totalPrecio = productos.stream()
.mapToDouble(Producto::getPrecio) // Stream<Producto> → DoubleStream
.sum(); // operación directa, sin boxing
int totalStock = productos.stream()
.mapToInt(Producto::getStock)
.sum();
OptionalDouble precioMedio = productos.stream()
.mapToDouble(Producto::getPrecio)
.average();
precioMedio.ifPresent(media ->
System.out.printf("Precio medio: $%.2f%n", media)
);flatMap() — aplanar streams de streams
// flatMap() transforma cada elemento en un Stream y los aplana en uno solo
// Útil cuando tienes listas dentro de listas
// Escenario: cada pedido tiene una lista de productos
List<Pedido> pedidos = Arrays.asList(
new Pedido(Arrays.asList("Laptop", "Mouse")),
new Pedido(Arrays.asList("Teclado")),
new Pedido(Arrays.asList("Monitor", "Webcam", "Auriculares"))
);
// Con map() obtendrías Stream<List<String>> — una lista de listas
// Con flatMap() obtienes Stream<String> — todos los productos planos
List<String> todosLosProductos = pedidos.stream()
.flatMap(pedido -> pedido.getProductos().stream())
.distinct()
.sorted()
.collect(Collectors.toList());
// ["Auriculares", "Laptop", "Monitor", "Mouse", "Teclado", "Webcam"]
// Otro ejemplo: dividir frases en palabras
List<String> frases = Arrays.asList("Java es genial", "Streams son potentes");
List<String> palabras = frases.stream()
.flatMap(frase -> Arrays.stream(frase.split(" ")))
.distinct()
.sorted()
.collect(Collectors.toList());sorted() — ordenar
// Ordenar con el orden natural (Comparable)
List<String> ordenados = nombres.stream()
.sorted() // orden alfabético
.collect(Collectors.toList());
// Ordenar con Comparator personalizado
List<Producto> porPrecio = productos.stream()
.sorted(Comparator.comparingDouble(Producto::getPrecio)) // ascendente
.collect(Collectors.toList());
List<Producto> porPrecioDesc = productos.stream()
.sorted(Comparator.comparingDouble(Producto::getPrecio).reversed()) // descendente
.collect(Collectors.toList());
// Ordenar por múltiples criterios
List<Producto> ordenadoMultiple = productos.stream()
.sorted(Comparator.comparing(Producto::getCategoria)
.thenComparingDouble(Producto::getPrecio))
.collect(Collectors.toList());distinct(), limit(), skip(), peek()
List<Integer> numeros = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 5, 5);
// distinct() — elimina duplicados (usa equals() y hashCode())
List<Integer> sinDuplicados = numeros.stream()
.distinct()
.collect(Collectors.toList());
// [1, 2, 3, 4, 5]
// limit(n) — solo los primeros n elementos
List<Producto> top5 = productos.stream()
.sorted(Comparator.comparingDouble(Producto::getPrecio).reversed())
.limit(5) // solo los 5 más caros
.collect(Collectors.toList());
// skip(n) — saltar los primeros n elementos (paginación)
List<Producto> pagina2 = productos.stream()
.skip(10) // saltar los 10 primeros
.limit(10) // tomar los siguientes 10
.collect(Collectors.toList());
// peek() — observar sin modificar (útil para debugging)
List<String> resultado = nombres.stream()
.filter(n -> n.length() > 3)
.peek(n -> System.out.println("Después del filter: " + n)) // debug
.map(String::toUpperCase)
.peek(n -> System.out.println("Después del map: " + n)) // debug
.collect(Collectors.toList());Operaciones terminales — producir el resultado
Las operaciones terminales cierran el Stream y producen un resultado. Después de una operación terminal, el Stream no se puede usar.
collect() — el más versátil
// collect() recopila los elementos del Stream en una colección u otro formato
// Usa Collectors para especificar el formato de salida
// → List
List<Producto> lista = productos.stream()
.filter(Producto::isActivo)
.collect(Collectors.toList());
// → Set (elimina duplicados automáticamente)
Set<String> categorias = productos.stream()
.map(Producto::getCategoria)
.collect(Collectors.toSet());
// → Map (clave → valor)
Map<Integer, Producto> porId = productos.stream()
.collect(Collectors.toMap(
Producto::getId, // función clave
p -> p // función valor (o Function.identity())
));
// → String concatenado
String nombresUnidos = productos.stream()
.map(Producto::getNombre)
.collect(Collectors.joining(", ", "[", "]"));
// "[Laptop, Mouse, Teclado, Monitor]"
// → Agrupar por categoría (groupingBy)
Map<String, List<Producto>> porCategoria = productos.stream()
.collect(Collectors.groupingBy(Producto::getCategoria));
// {"tecnologia": [Laptop, Monitor], "periféricos": [Mouse, Teclado], ...}
// → Contar por categoría
Map<String, Long> cantidadPorCategoria = productos.stream()
.collect(Collectors.groupingBy(
Producto::getCategoria,
Collectors.counting() // downstream collector
));
// {"tecnologia": 2, "periféricos": 3, ...}
// → Precio medio por categoría
Map<String, Double> mediaPorCategoria = productos.stream()
.collect(Collectors.groupingBy(
Producto::getCategoria,
Collectors.averagingDouble(Producto::getPrecio)
));
// → Particionar en dos grupos (verdadero/falso)
Map<Boolean, List<Producto>> activosInactivos = productos.stream()
.collect(Collectors.partitioningBy(Producto::isActivo));
List<Producto> activos = activosInactivos.get(true);
List<Producto> inactivos = activosInactivos.get(false);forEach() — ejecutar acción por cada elemento
// forEach() — operación terminal que ejecuta una acción sin devolver valor
productos.stream()
.filter(p -> p.getStock() == 0)
.forEach(p -> System.out.println("Sin stock: " + p.getNombre()));
// forEachOrdered() — respeta el orden de encuentro (importante en paralelo)
productos.parallelStream()
.forEachOrdered(p -> System.out.println(p.getNombre()));reduce() — combinar elementos en uno
// reduce() combina todos los elementos en un único resultado
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
// Suma manual con reduce
Optional<Integer> suma = numeros.stream()
.reduce((a, b) -> a + b);
// Optional[15]
// Con valor identidad (evita Optional)
int suma2 = numeros.stream()
.reduce(0, Integer::sum);
// 15
// Multiplicar todos los elementos
int producto = numeros.stream()
.reduce(1, (a, b) -> a * b);
// 120
// Concatenar strings
String cadena = Stream.of("Java", " ", "Streams", " ", "rocks")
.reduce("", String::concat);
// "Java Streams rocks"Operaciones de búsqueda y verificación
List<Producto> productos = obtenerProductos();
// findFirst() — primer elemento que cumple la condición
Optional<Producto> primero = productos.stream()
.filter(p -> p.getPrecio() < 50)
.findFirst();
primero.ifPresent(p ->
System.out.println("Primer producto barato: " + p.getNombre())
);
// findAny() — cualquier elemento (más eficiente en paralelo)
Optional<Producto> cualquiera = productos.parallelStream()
.filter(Producto::isActivo)
.findAny();
// anyMatch() — ¿algún elemento cumple la condición?
boolean hayProductosCaro = productos.stream()
.anyMatch(p -> p.getPrecio() > 1000);
// allMatch() — ¿todos cumplen la condición?
boolean todosActivos = productos.stream()
.allMatch(Producto::isActivo);
// noneMatch() — ¿ninguno cumple la condición?
boolean sinStockCero = productos.stream()
.noneMatch(p -> p.getStock() == 0);
// count() — número de elementos
long totalActivos = productos.stream()
.filter(Producto::isActivo)
.count();
// min() y max()
Optional<Producto> masBarato = productos.stream()
.min(Comparator.comparingDouble(Producto::getPrecio));
Optional<Producto> masCaro = productos.stream()
.max(Comparator.comparingDouble(Producto::getPrecio));El modelo de datos — clase Producto para los ejemplos
// La clase Producto usada en todos los ejemplos anteriores
public class Producto {
private int id;
private String nombre;
private double precio;
private int stock;
private String categoria;
private boolean activo;
// Constructor
public Producto(int id, String nombre, double precio, int stock, String categoria) {
this.id = id;
this.nombre = nombre;
this.precio = precio;
this.stock = stock;
this.categoria = categoria;
this.activo = true;
}
// Getters
public int getId() { return id; }
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
public int getStock() { return stock; }
public String getCategoria() { return categoria; }
public boolean isActivo() { return activo; }
@Override
public String toString() {
return String.format("Producto{id=%d, nombre='%s', precio=%.2f}", id, nombre, precio);
}
}
// Datos de prueba
List<Producto> productos = Arrays.asList(
new Producto(1, "Laptop Pro", 899.99, 20, "tecnologia"),
new Producto(2, "Mouse Wireless", 29.99, 150, "periféricos"),
new Producto(3, "Teclado Mecánico", 79.99, 80, "periféricos"),
new Producto(4, "Monitor 4K", 399.99, 25, "tecnologia"),
new Producto(5, "Webcam HD", 49.99, 60, "periféricos"),
new Producto(6, "SSD 1TB", 89.99, 35, "almacenamiento"),
new Producto(7, "Hub USB-C", 29.99, 75, "periféricos"),
new Producto(8, "Auriculares BT", 59.99, 40, "audio")
);Ejemplo completo — pipeline real de análisis de datos
import java.util.*;
import java.util.stream.*;
public class AnálisisProductos {
public static void main(String[] args) {
List<Producto> productos = obtenerProductos();
System.out.println("═══ ANÁLISIS DE INVENTARIO ═══\n");
// 1. Resumen general
long total = productos.stream().count();
long activos = productos.stream().filter(Producto::isActivo).count();
double valorInventario = productos.stream()
.mapToDouble(p -> p.getPrecio() * p.getStock())
.sum();
System.out.printf("Total productos: %d%n", total);
System.out.printf("Activos: %d%n", activos);
System.out.printf("Valor inventario: $%.2f%n%n", valorInventario);
// 2. Top 3 productos más caros por categoría
System.out.println("Top 3 más caros por categoría:");
productos.stream()
.collect(Collectors.groupingBy(
Producto::getCategoria,
Collectors.collectingAndThen(
Collectors.toList(),
lista -> lista.stream()
.sorted(Comparator.comparingDouble(Producto::getPrecio).reversed())
.limit(3)
.collect(Collectors.toList())
)
))
.forEach((categoria, top) -> {
System.out.println(" " + categoria + ":");
top.forEach(p ->
System.out.printf(" - %s: $%.2f%n", p.getNombre(), p.getPrecio())
);
});
// 3. Estadísticas de precios por categoría
System.out.println("\nEstadísticas por categoría:");
productos.stream()
.collect(Collectors.groupingBy(
Producto::getCategoria,
Collectors.summarizingDouble(Producto::getPrecio)
))
.forEach((cat, stats) ->
System.out.printf(" %-20s Min: $%.2f Max: $%.2f Media: $%.2f%n",
cat, stats.getMin(), stats.getMax(), stats.getAverage())
);
// 4. Productos con stock crítico (<= 30 unidades)
System.out.println("\nStock crítico (≤ 30 unidades):");
String alertas = productos.stream()
.filter(p -> p.getStock() <= 30)
.sorted(Comparator.comparingInt(Producto::getStock))
.map(p -> String.format("%s (%d uds)", p.getNombre(), p.getStock()))
.collect(Collectors.joining(", "));
System.out.println(" " + alertas);
// 5. ¿Hay algún producto sin stock?
boolean haySinStock = productos.stream()
.anyMatch(p -> p.getStock() == 0);
System.out.println("\n¿Hay productos sin stock? " + haySinStock);
// 6. Precio medio de periféricos activos
OptionalDouble mediaPerifericos = productos.stream()
.filter(p -> "periféricos".equals(p.getCategoria()) && p.isActivo())
.mapToDouble(Producto::getPrecio)
.average();
mediaPerifericos.ifPresent(media ->
System.out.printf("%nPrecio medio periféricos: $%.2f%n", media)
);
}
}Streams paralelos — procesamiento concurrente
// Los Streams paralelos dividen el trabajo entre múltiples hilos automáticamente
// Usan el ForkJoinPool común de Java
// Crear stream paralelo desde una colección
Stream<Producto> streamParalelo = productos.parallelStream();
// O convertir un stream secuencial a paralelo
Stream<Producto> paralelo2 = productos.stream().parallel();
// Volver a secuencial
Stream<Producto> secuencial = paralelo2.sequential();
// Ejemplo: suma en paralelo (beneficioso con millones de elementos)
double total = IntStream.rangeClosed(1, 1_000_000)
.parallel()
.asDoubleStream()
.sum();
// ⚠️ Cuándo usar streams paralelos:
// ✅ Colecciones muy grandes (miles/millones de elementos)
// ✅ Operaciones computacionalmente costosas por elemento
// ✅ Operaciones independientes (sin estado compartido entre elementos)
// ❌ Cuándo NO usar streams paralelos:
// ❌ Colecciones pequeñas — la sobrecarga de crear hilos supera el beneficio
// ❌ Operaciones con estado compartido — condiciones de carrera
// ❌ Orden garantizado con forEachOrdered — cancela el beneficio del paralelo
// ❌ Operaciones de E/S — el paralelismo no ayuda cuando esperas I/O
// Medir para decidir — no asumir que paralelo siempre es mejor
long inicio = System.nanoTime();
long suma = LongStream.rangeClosed(1, 10_000_000).parallel().sum();
long tiempo = System.nanoTime() - inicio;
System.out.printf("Suma paralela: %d ms%n", tiempo / 1_000_000);Optional — el compañero inseparable de Streams
// Muchas operaciones terminales devuelven Optional<T>
// Optional representa un valor que puede o no estar presente
Optional<Producto> masBarato = productos.stream()
.min(Comparator.comparingDouble(Producto::getPrecio));
// Formas de trabajar con Optional:
// 1. isPresent() + get() — el menos recomendado
if (masBarato.isPresent()) {
Producto p = masBarato.get();
}
// 2. ifPresent() — ejecutar acción si hay valor
masBarato.ifPresent(p ->
System.out.println("Más barato: " + p.getNombre())
);
// 3. orElse() — valor por defecto si está vacío
Producto p = masBarato.orElse(new Producto(0, "Sin productos", 0, 0, ""));
// 4. orElseGet() — generar el valor por defecto solo si se necesita (lazy)
Producto p2 = masBarato.orElseGet(() -> crearProductoPorDefecto());
// 5. orElseThrow() — lanzar excepción si está vacío
Producto p3 = masBarato.orElseThrow(() ->
new RuntimeException("No hay productos")
);
// 6. map() en Optional — transformar si tiene valor
Optional<String> nombreMasBarato = masBarato.map(Producto::getNombre);
// 7. filter() en Optional — filtrar la condición
Optional<Producto> barataYActiva = masBarato
.filter(Producto::isActivo);Errores comunes con Streams
❌ Reutilizar un Stream después de la operación terminal
Stream<Producto> stream = productos.stream().filter(Producto::isActivo);
long count = stream.count(); // operación terminal — cierra el stream
List<Producto> lista = stream.collect(Collectors.toList()); // ❌ IllegalStateException
// ✅ Crear un nuevo Stream para cada pipeline
long count = productos.stream().filter(Producto::isActivo).count();
List<Producto> lista = productos.stream().filter(Producto::isActivo).collect(Collectors.toList());❌ Modificar la colección fuente dentro del Stream
// ❌ Modificar la lista mientras el Stream la recorre
List<String> lista = new ArrayList<>(Arrays.asList("a", "b", "c"));
lista.stream()
.filter(s -> s.equals("a"))
.forEach(s -> lista.remove(s)); // ConcurrentModificationException
// ✅ Recolectar el resultado y luego modificar
List<String> aEliminar = lista.stream()
.filter(s -> s.equals("a"))
.collect(Collectors.toList());
lista.removeAll(aEliminar);❌ Usar peek() para lógica de negocio
// ❌ peek() es solo para debugging — no para efectos secundarios
productos.stream()
.filter(Producto::isActivo)
.peek(p -> guardarEnBD(p)) // ❌ el comportamiento es impredecible
.collect(Collectors.toList());
// ✅ forEach() o map() para efectos secundarios
productos.stream()
.filter(Producto::isActivo)
.forEach(p -> guardarEnBD(p)); // ✅ claro y predecible❌ No usar streams numéricos cuando se necesita aritmética
// ❌ Boxing/unboxing innecesario — menos eficiente
double sum1 = productos.stream()
.map(Producto::getPrecio) // Stream<Double> — boxing
.reduce(0.0, Double::sum);
// ✅ Stream numérico — sin boxing, más eficiente
double sum2 = productos.stream()
.mapToDouble(Producto::getPrecio) // DoubleStream — primitivos directos
.sum();Streams vs bucles for — cuándo usar cada uno
| Criterio | Streams | Bucle for / for-each |
|---|---|---|
| Legibilidad | ✅ Declarativo — qué hacer | 🔶 Imperativo — cómo hacer |
| Transformaciones encadenadas | ✅ Natural y limpio | ❌ Requiere variables auxiliares |
| Control de flujo complejo | 🔶 Puede ser forzado | ✅ break, continue, return |
| Modificar variables externas | ❌ No recomendado | ✅ Natural |
| Paralelización | ✅ .parallelStream() | ❌ Complejo manualmente |
| Rendimiento (colecciones pequeñas) | 🔶 Pequeña sobrecarga | ✅ Más directo |
| Rendimiento (colecciones grandes) | ✅ Paralelo ayuda | 🔶 Manual |
| Operaciones de conteo, suma, media | ✅ count(), sum(), average() | ❌ Variable acumuladora |
Resumen: lo que aprendiste hoy
- ✅ Un Stream es una secuencia de operaciones sobre datos — no almacena, no modifica la fuente
- ✅ Todo pipeline tiene 3 partes: fuente → operaciones intermedias → operación terminal
- ✅ Las operaciones intermedias son lazy — no se ejecutan hasta la operación terminal
- ✅ filter() filtra, map() transforma, flatMap() aplana, sorted() ordena
- ✅ collect() es la operación terminal más versátil — usa Collectors para listas, maps, agrupaciones
- ✅ Collectors.groupingBy() agrupa, partitioningBy() divide en dos grupos
- ✅ mapToInt/Double/Long() para aritmética eficiente sin boxing/unboxing
- ✅ reduce() combina, findFirst()/findAny() busca, anyMatch()/allMatch() verifica
- ✅ Los Streams devuelven Optional en muchas operaciones — usa
ifPresent(),orElse()yorElseThrow() - ✅ parallelStream() para colecciones grandes con operaciones costosas — mide antes de usar
- ✅ Un Stream no se puede reusar después de la operación terminal
🧪 ¿Tienes los fundamentos de Java bien sólidos para dominar Streams?
Los Streams son Java intermedio-avanzado. Para aprovecharlos necesitas dominar colecciones, lambdas, interfaces funcionales y el manejo de excepciones. Comprueba dónde estás:
👉 Test: Java Básico 👉 Test: Java Core Intermedio
¿Ya usabas Streams en tus proyectos Java o era la primera vez que los veías? ¿Cuál operación te pareció más poderosa: groupingBy(), flatMap() o los streams paralelos? Cuéntanos en los comentarios 👇 — respondemos todos. ☕🚀
No hay comentarios todavía. Sé el primero en compartir tu opinión.