¿Cómo programar con .stream() en Java?

Una de las primeras paradas del arco de Streams va a ser entender qué consecuencias tiene llamar a .stream() y en qué tipo de clases le podemos llamar. ¿Qué se puede hacer una vez que tenemos una referencia a un Stream? En esta lección introduzco a la API de Streams mostrando ejemplos de su uso.

La API de Streams en Java nos ofrece una forma elegante y eficiente de procesar colecciones de datos mediante una sintaxis que recuerda a una tubería. Esta tubería representa una secuencia de pasos que describen cómo queremos transformar y consumir los elementos de una colección iterable, como una lista o un conjunto.

Para empezar a trabajar con streams, lo primero que hacemos es obtener un stream a partir de una colección usando el método stream(). Por ejemplo, si tenemos una lista de países, al llamar a paises.stream() obtenemos un stream que itera sobre cada uno de esos elementos. Este stream es genérico y mantiene el tipo de los elementos de la colección original, por lo que si la lista es de cadenas de texto, el stream será de tipo Stream<String>. Esto también aplica para otros tipos de colecciones, como conjuntos o mapas, donde el tipo genérico del stream se ajusta a los elementos que contienen.

Una característica fundamental de los streams es que podemos encadenar operaciones intermedias que transforman o filtran los datos. Estas operaciones no consumen el stream inmediatamente, sino que construyen una descripción del procesamiento que queremos realizar. Por ejemplo, la operación filter recibe un predicado, una función que devuelve un valor booleano, y devuelve un nuevo stream con los elementos que cumplen esa condición. Otra operación intermedia común es distinct, que elimina elementos duplicados sin necesidad de parámetros adicionales, devolviendo un stream con elementos únicos.

Finalmente, para obtener un resultado o consumir el stream, usamos operaciones terminales. Estas pueden devolver una colección, un valor o simplemente realizar una acción sin devolver nada. Por ejemplo, toList() recolecta todos los elementos procesados y los devuelve en una lista. La operación count() devuelve el número de elementos que han pasado por el stream. Por otro lado, forEach() ejecuta una función para cada elemento del stream sin devolver ningún resultado.

Veamos un ejemplo práctico. Supongamos que queremos imprimir cada país de una lista. Podemos hacerlo llamando a forEach con una función lambda que reciba cada país y lo imprima:

paises.stream()
      .forEach(pais -> System.out.println("País: " + pais));

Si la lista contiene elementos duplicados, como dos veces Bolivia, la función se ejecutará para cada uno, imprimiendo Bolivia dos veces. Para evitar esto, podemos usar la operación intermedia distinct antes de forEach:

paises.stream()
      .distinct()
      .forEach(pais -> System.out.println("País: " + pais));

Así, solo se imprimirá una vez cada país único.

También podemos filtrar los elementos según una condición. Por ejemplo, para quedarnos solo con los países cuyo nombre tenga como máximo seis caracteres, usamos filter con un predicado que evalúe la longitud del nombre:

paises.stream()
      .filter(pais -> pais.length() <= 6)
      .forEach(pais -> System.out.println("País: " + pais));

Esto excluirá países con nombres más largos, como Argentina o Colombia. Si invertimos la condición, mostrando solo los que tienen más de seis caracteres, el código sería:

paises.stream()
      .filter(pais -> pais.length() > 6)
      .forEach(pais -> System.out.println("País: " + pais));

La forma habitual de escribir estas cadenas de operaciones es colocando cada llamada en una línea separada, comenzando con un punto, para mejorar la legibilidad:

paises.stream()
      .distinct()
      .filter(pais -> pais.length() <= 6)
      .forEach(pais -> System.out.println("País: " + pais));

La API de Streams es muy amplia y ofrece muchas más operaciones intermedias y terminales para transformar y consumir datos de formas muy variadas. Explorarla nos permitirá escribir código más limpio y expresivo para manejar colecciones en Java.

Lista de reproducción
  1. 1
    ¿Cómo funciona la API de Streams de Java?
    7 minutos
  2. 2
    ¿Cómo programar con .stream() en Java?
    8 minutos