W3docs

Creación de streams en Java

Crea streams en Java desde colecciones, arrays, Stream.of, Stream.iterate, Stream.generate y fuentes de I/O.

El capítulo introductorio mostró la forma del pipeline — fuente → intermedias → terminal — y trató la fuente como algo dado. Este capítulo es el catálogo de fuentes. Todo pipeline de stream que escribas comienza con una de ellas, y cada una tiene un pequeño conjunto de características que determinan si el pipeline resultante es correcto, perezoso, finito, ordenado o paralelizable.

La lista es más corta de lo que parece. Casi todos los streams que escribirás comienzan con coll.stream(), Stream.of(...), Arrays.stream(arr) o un IntStream.range. El resto de este capítulo es "y aquí están las pocas situaciones donde las otras opciones son la elección correcta."

Desde una Collectioncoll.stream()

La fuente dominante. Collection<T> tiene un método stream() por defecto, por lo que cada List, Set, Queue y Deque lo expone de forma gratuita:

List<String> names = List.of("Alice", "Bob", "Carol");
long count = names.stream().filter(n -> n.length() > 3).count();

El stream es secuencial, de tamaño conocido (la JVM sabe el número de elementos de antemano) y ordenado si la colección lo es. Un List produce un stream ordenado; un HashSet produce uno sin orden; un TreeSet produce uno ordenado según el comparador del conjunto.

También existe coll.parallelStream(), que planifica las tareas en el ForkJoinPool común. Misma fuente, diferente política de ejecución — se cubre en Java Parallel Streams.

Desde elementos explícitos — Stream.of(...)

Usa Stream.of cuando tengas una lista corta y conocida de elementos y no quieras crear un List desechable:

Stream<String> s   = Stream.of("a", "b", "c");
Stream<Integer> n  = Stream.of(1, 2, 3, 4, 5);
Stream<Object> one = Stream.of("just one");

Es un método varargs, por lo que acepta cualquier número de argumentos (cero está permitido y produce un stream vacío). Con un único argumento T[], el compilador elige Stream.of(T...) en lugar de Stream.of(T) — útil cuando ya tienes un array:

String[] arr = {"x", "y", "z"};
Stream<String> fromArr = Stream.of(arr);   // same as Arrays.stream(arr)

Desde un array — Arrays.stream(...)

Arrays.stream tiene sobrecargas para T[], int[], long[], double[], además de variantes con rango:

int[] xs = {3, 1, 4, 1, 5, 9, 2, 6};
IntStream ix = Arrays.stream(xs);              // primitive specialisation
IntStream tail = Arrays.stream(xs, 2, xs.length);   // half-open [2, len)

String[] words = {"alpha", "beta", "gamma"};
Stream<String> ws = Arrays.stream(words);

Las sobrecargas primitivas devuelven IntStream, LongStream, DoubleStream — no Stream<Integer>. Esto importa: los streams primitivos evitan el boxing, tienen sum, average, min, max directamente (sin collector) y funcionan bien con mapToInt/mapToObj para moverse entre mundos.

Rangos primitivos — IntStream.range / rangeClosed

La forma más rápida de iterar por índice sin un bucle for:

// 0, 1, 2, ..., 9
IntStream.range(0, 10).forEach(i -> System.out.println(i));

// 1..10 inclusive
int sum = IntStream.rangeClosed(1, 10).sum();   // 55

range(a, b) es semiabierto [a, b). rangeClosed(a, b) es [a, b]. Ambos son acotados, ordenados, de tamaño conocido y más rápidos que Stream.iterate(0, i -> i + 1).limit(n) porque la JVM conoce el número de elementos de antemano. Úsalos siempre que el cuerpo de un bucle sea "hacer algo en el índice i."

Para asociar un índice a los elementos de un List se escribe:

List<String> names = List.of("Alice", "Bob", "Carol");
IntStream.range(0, names.size())
    .mapToObj(i -> i + ": " + names.get(i))
    .forEach(System.out::println);

Streams infinitos generados — Stream.iterate y Stream.generate

Dos formas de producir un stream ilimitado. Se parecen; no son lo mismo.

Stream.iterate(seed, f) — comienza con seed, luego f(seed), luego f(f(seed)), …. Ordenado, determinista, secuencial. Casi siempre seguido de un cortocircuito:

Stream.iterate(1, n -> n * 2)
    .limit(10)
    .forEach(System.out::println);   // 1, 2, 4, 8, ..., 512

También existe una sobrecarga de 3 argumentos Stream.iterate(seed, hasNext, next) (Java 9+) que incorpora la condición de parada en la fuente — no se necesita limit:

Stream.iterate(1, n -> n < 1000, n -> n * 2).forEach(System.out::println);

Stream.generate(supplier) — llama a un Supplier<T> repetidamente. Sin orden, sin relación entre elementos:

Stream.generate(Math::random).limit(5).forEach(System.out::println);
Stream.generate(() -> "ping").limit(3).forEach(System.out::println);

Usa iterate para secuencias donde cada término depende del anterior (n -> n + 1, n -> n * 2, el par de Fibonacci arr -> {arr[1], arr[0] + arr[1]}). Usa generate para valores independientes de una fuente auxiliar — números aleatorios, constantes fijas, UUIDs.

En cualquier caso, siempre termínalos con una operación de cortocircuito: limit(n), el iterate de 3 argumentos, o un terminal como findFirst / anyMatch. Un toList() simple sobre un stream infinito bloquea la JVM.

Desde I/O — Files.lines, BufferedReader.lines

Files.lines(path) abre un archivo y devuelve un Stream<String> con sus líneas. Perezoso: las líneas se leen conforme el pipeline las necesita, no de antemano:

try (Stream<String> lines = Files.lines(Path.of("words.txt"))) {
    long longWords = lines.filter(w -> w.length() > 8).count();
}

El try-with-resources es obligatorio. El stream mantiene abierto un descriptor de archivo, y la única forma de liberarlo es llamar a close() — lo que try-with-resources hace por ti. Sin ello, el descriptor se filtra hasta que el stream sea recolectado por el garbage collector, lo que puede no ocurrir nunca bajo carga.

Misma forma para Readers mediante BufferedReader.lines(). Ambos son la manera canónica de recorrer un archivo de texto sin cargarlo en memoria.

String.chars() y String.codePoints()

Un String es una secuencia de unidades de código UTF-16; la API expone ambas vistas:

"hello".chars()                   // IntStream of UTF-16 code units
       .filter(Character::isUpperCase)
       .count();

"héllo".codePoints()              // IntStream of Unicode code points
       .mapToObj(Character::toString)
       .forEach(System.out::println);

Ambos devuelven IntStream. chars() está bien para ASCII; para cualquier cosa que pueda contener pares sustitutos (la mayoría de los emoji, muchos scripts), codePoints() es la opción segura.

Streams vacíos y de un solo elemento

Para casos por defecto y ramas de flatMap:

Stream<String> none = Stream.empty();          // 0 elements
Stream<String> one  = Stream.of("x");          // exactly 1
Stream<String> opt  = Optional.of("x").stream();   // 1 if present, else empty

Optional.stream() (Java 9+) es el puente entre Optional<T> y Stream<T> — útil cuando aplicas flatMap a un stream de Optionals para obtener un stream de valores presentes sin ningún manejo de null.

Stream.Builder — insertando elementos uno a uno

Cuando no puedes expresar la fuente como un literal, un array o un generador — normalmente porque los elementos provienen de distintas ramas de código imperativo — existe un builder:

Stream.Builder<String> b = Stream.builder();
b.add("first");
if (someCondition) b.add("second");
b.accept("third");
Stream<String> s = b.build();

Tras build(), el builder queda sellado; un add posterior lanza una excepción. Es una herramienta poco frecuente pero legítima. La mayor parte del código que recurre a ella se escribe mejor con un ArrayList<String> seguido de list.stream(), pero el builder evita ese intermedio cuando los datos se construyen de forma fragmentada.

Streams de Map — no existe uno

Map<K, V> no tiene un método stream(). En su lugar, debes hacer stream de sus vistas:

Map<String, Integer> ages = Map.of("Alice", 30, "Bob", 25);
ages.entrySet().stream().filter(e -> e.getValue() >= 18).map(Map.Entry::getKey).toList();
ages.keySet().stream().sorted().toList();
ages.values().stream().mapToInt(Integer::intValue).sum();

entrySet().stream() es lo que necesitarás la mayoría de las veces — ambas mitades de cada entrada están disponibles, y Map.Entry::getKey / ::getValue funcionan como referencias a métodos.

Elegir la fuente correcta

SituaciónUsar
Ya tienes un List, Set, Queuecoll.stream()
Tienes unos pocos elementos fijosStream.of(a, b, c)
Tienes un T[]Arrays.stream(arr)
Tienes int[], long[], double[]Arrays.stream(arr) → stream primitivo
Quieres iterar por índiceIntStream.range(0, n)
Quieres cada término a partir del anteriorStream.iterate
Quieres muestras independientesStream.generate
Quieres las líneas de un archivo de textoFiles.lines(path) dentro de try-with-resources
Quieres los caracteres de un String"...".chars() o .codePoints()
Quieres un stream vacío por defectoStream.empty()
Estás construyendo datos de forma fragmentadaStream.builder()
Quieres hacer stream de un Mapmap.entrySet().stream()

Esa tabla cubre todo lo del capítulo, y probablemente el 99% del código real.

Un ejemplo completo: diez fuentes, un programa

El programa a continuación construye un stream desde cada una de las fuentes principales, ejecuta una pequeña operación terminal sobre él para que la salida sea visible, e imprime tanto el resultado como el tipo de fuente utilizada.

java— editable, runs on the server

Lo que se puede aprender de la ejecución:

  • Cada línea de la salida provino de una fuente diferente, pero todas fluyen hacia el mismo vocabulario de intermedias/terminal. La elección de la fuente determina con qué puede comenzar el pipeline; no cambia lo que viene después.
  • Arrays.stream(int[]) produjo un IntStreamsum() está directamente en el stream, sin boxing, sin Collectors.summingInt. Las especializaciones primitivas importan en pipelines numéricos.
  • Las dos llamadas a Stream.iterate muestran la diferencia entre iterate(seed, f) + limit(n) (tú eliges el número) y el iterate(seed, hasNext, next) de 3 argumentos (la fuente elige el número). Ambas son acotadas; un iterate sin límite y sin un terminal de cortocircuito es el clásico bug que bloquea la JVM para siempre.
  • Stream.empty() y Optional.of(...).stream() son la manera en que los streams vacíos y de un solo elemento entran en un pipeline — típicamente dentro de una rama de flatMap donde algunas entradas producen cero o un elemento descendiente.
  • Stream.builder() es la salida de emergencia para el caso (poco frecuente) en que la fuente se construye imperativamente a través de ramas. La mayor parte del código real recurre primero a coll.stream().

Qué sigue

Ahora puedes construir cualquier stream que necesites desde cualquier fuente que tengas disponible. Los dos capítulos siguientes cubren las operaciones que se ejecutan entre la fuente y el resultado. Primero, Java Stream Intermediate Operationsfilter, map, flatMap, distinct, sorted, peek, limit, skip — las transformaciones perezosas que dan forma al stream sin ejecutarlo. Luego los terminales que producen el valor.

Práctica

Práctica
¿Cuál de estas es la forma *más eficiente* de iterar los enteros `0` hasta `99`, en orden, como stream?
¿Cuál de estas es la forma *más eficiente* de iterar los enteros `0` hasta `99`, en orden, como stream?
Was this page helpful?