Stream API
Procesamiento declarativo de colecciones (java.util.stream)
Qué es un Stream
Un Stream<T> es una secuencia de elementos sobre la que se declaran operaciones en cadena.
No es una colección: no almacena datos ni los modifica. Es un pipeline de transformaciones
que se ejecuta de forma perezosa (lazy) hasta que una operación terminal lo materializa.
Distinción importante: java.util.stream (Stream API de colecciones) es completamente distinto de java.io (I/O
streams de bytes/caracteres).
import java.util.stream.Stream;
// Desde una colección
List<String> lista = List.of("Ana", "Luis", "Eva");
Stream<String> s1 = lista.stream();
// Desde valores literales
Stream<String> s2 = Stream.of("a", "b", "c");
// Stream infinito con límite
Stream<Integer> numeros = Stream.iterate(0, n -> n + 1).limit(10);
// Stream vacío
Stream<String> vacio = Stream.empty(); Operaciones intermedias
Devuelven otro Stream y son lazy: no se ejecutan hasta que aparece una
operación terminal. Las más usadas: filter, map, sorted,
distinct, limit, skip.
List<String> nombres = List.of("Ana", "Carlos", "Eva", "Alberto", "Beatriz");
// filter: descarta elementos que no cumplen el predicado
List<String> conA = nombres.stream()
.filter(n -> n.startsWith("A"))
.collect(Collectors.toList());
// ["Ana", "Alberto"]
// map: transforma cada elemento
List<Integer> longitudes = nombres.stream()
.map(String::length)
.collect(Collectors.toList());
// [3, 6, 3, 7, 7]
// sorted: ordena (usa Comparable o Comparator)
List<String> ordenados = nombres.stream()
.sorted()
.collect(Collectors.toList());
// distinct + limit
List<String> primerosSinDuplicados = nombres.stream()
.distinct()
.limit(3)
.collect(Collectors.toList()); Operaciones terminales
Desencadenan la ejecución del pipeline y producen un resultado final: una colección, un escalar, un
Optional o un efecto secundario. Un stream solo puede consumirse una vez.
// forEach: terminal con efecto secundario (no devuelve Stream)
nombres.stream().forEach(System.out::println);
// collect: materializa el Stream en una colección
List<String> lista = stream.collect(Collectors.toList());
Set<String> conjunto = stream.collect(Collectors.toSet());
// count
long total = nombres.stream().filter(n -> n.length() > 3).count();
// findFirst / findAny: devuelven Optional
Optional<String> primero = nombres.stream().filter(n -> n.startsWith("E")).findFirst();
// anyMatch / allMatch / noneMatch
boolean hayLargo = nombres.stream().anyMatch(n -> n.length() > 5);
boolean todosMayus = nombres.stream().allMatch(n -> n.equals(n.toUpperCase()));
// reduce: combina todos los elementos en uno
Optional<Integer> suma = Stream.of(1, 2, 3, 4).reduce(Integer::sum);
int sumaConIdentidad = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); Collectors avanzados
La clase Collectors proporciona implementaciones de Collector listas para usar: agrupación,
particionado, conteo, concatenación de strings y construcción de mapas.
import java.util.stream.Collectors;
List<String> nombres = List.of("Ana", "Carlos", "Eva", "Alberto");
// joining: concatena en un String
String csv = nombres.stream().collect(Collectors.joining(", "));
// "Ana, Carlos, Eva, Alberto"
// groupingBy: agrupa en un Map
Map<Integer, List<String>> porLongitud = nombres.stream()
.collect(Collectors.groupingBy(String::length));
// {3=[Ana, Eva], 6=[Carlos], 7=[Alberto]}
// counting por grupo
Map<Integer, Long> conteo = nombres.stream()
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
// toMap
Map<String, Integer> nombreALongitud = nombres.stream()
.collect(Collectors.toMap(n -> n, String::length)); flatMap: aplanar streams anidados
map produce un Stream<Stream<T>> cuando la función devuelve un stream.
flatMap lo aplana en un Stream<T> — equivale al concatMap de otros
lenguajes.
// flatMap: "aplana" streams anidados
List<List<String>> anidado = List.of(
List.of("a", "b"),
List.of("c", "d"),
List.of("e")
);
List<String> plano = anidado.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// ["a", "b", "c", "d", "e"]
// Caso real: todas las palabras de varias frases
List<String> frases = List.of("hola mundo", "java ocho");
List<String> palabras = frases.stream()
.flatMap(f -> Arrays.stream(f.split(" ")))
.collect(Collectors.toList());
// ["hola", "mundo", "java", "ocho"]