Guía Completa sobre la API de Streams de Java

Introducción

La Stream API de Java, introducida en Java 8, permite procesar colecciones de datos (como listas, conjuntos, etc.) de manera declarativa. En lugar de iterar manualmente sobre una colección, puedes usar streams para trabajar con los elementos de forma más clara, concisa y eficiente.

En esta guía, exploraremos los conceptos fundamentales de la API de Streams, proporcionando ejemplos claros y ejercicios prácticos para ayudarte a comprender y utilizar esta poderosa herramienta.


1. ¿Qué es un Stream?

Un Stream es una secuencia de elementos que puede ser procesada de manera secuencial o paralela. A diferencia de las colecciones tradicionales (listas, conjuntos), un stream no almacena datos, sino que los procesa. El propósito de un stream es permitir operaciones de manera fluida y eficiente sobre los datos.

Características de un Stream:


2. Crear un Stream

En Java, puedes crear un Stream de varias maneras:

2.1 Crear un Stream a partir de una colección

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
        
        // Crear un Stream
        numeros.stream()
               .forEach(System.out::println);  // Imprime los elementos
    }
}

2.2 Crear un Stream a partir de un array

int[] numeros = {1, 2, 3, 4, 5};
Arrays.stream(numeros)
      .forEach(System.out::println);

2.3 Crear un Stream utilizando el método Stream.of()

Stream<String> nombres = Stream.of("Juan", "Maria", "Pedro");
nombres.forEach(System.out::println);

3. Operaciones Intermedias

Las operaciones intermedias transforman un Stream, pero no lo consumen. Estas operaciones son perezosas, es decir, no se ejecutan hasta que se invoque una operación terminal. Algunos ejemplos comunes de operaciones intermedias son:

3.1 filter(): Filtrar elementos

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6);
numeros.stream()
       .filter(n -> n % 2 == 0)  // Filtra los números pares
       .forEach(System.out::println);  // Imprime: 2, 4, 6

3.2 map(): Transformar elementos

List<String> palabras = Arrays.asList("java", "stream", "api");
palabras.stream()
        .map(String::toUpperCase)  // Convierte cada palabra a mayúsculas
        .forEach(System.out::println);  // Imprime: JAVA, STREAM, API

3.3 distinct(): Eliminar duplicados

List<Integer> numeros = Arrays.asList(1, 2, 3, 3, 4, 4, 5);
numeros.stream()
       .distinct()  // Elimina los duplicados
       .forEach(System.out::println);  // Imprime: 1, 2, 3, 4, 5

3.4 sorted(): Ordenar elementos

List<Integer> numeros = Arrays.asList(5, 1, 4, 2, 3);
numeros.stream()
       .sorted()  // Ordena de menor a mayor
       .forEach(System.out::println);  // Imprime: 1, 2, 3, 4, 5

3.5 limit(): Limitar el número de elementos

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
numeros.stream()
       .limit(3)  // Toma solo los primeros 3 elementos
       .forEach(System.out::println);  // Imprime: 1, 2, 3

4. Operaciones Terminales

Las operaciones terminales son aquellas que consumen el Stream y producen un resultado (como una colección, un valor agregado, etc.). A continuación, exploraremos algunas operaciones terminales comunes:

4.1 forEach(): Ejecutar una acción sobre cada elemento

List<String> palabras = Arrays.asList("java", "stream", "api");
palabras.stream()
        .forEach(System.out::println);  // Imprime: java, stream, api

4.2 collect(): Recoger los elementos en una colección

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> cuadrados = numeros.stream()
                                 .map(n -> n * n)
                                 .collect(Collectors.toList());  // Recoge los elementos en una lista
System.out.println(cuadrados);  // Imprime: [1, 4, 9, 16, 25]

4.3 reduce(): Reducir los elementos a un único valor

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
int suma = numeros.stream()
                  .reduce(0, Integer::sum);  // Suma los números
System.out.println(suma);  // Imprime: 15

4.4 anyMatch(), allMatch(), noneMatch(): Comprobaciones booleanas

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
boolean hayPares = numeros.stream()
                          .anyMatch(n -> n % 2 == 0);  // Comprueba si hay algún número par
System.out.println(hayPares);  // Imprime: true

5. Streams Paralelos

La Stream API de Java también permite trabajar con streams paralelos. Esto puede ser útil para mejorar el rendimiento cuando se trabaja con grandes volúmenes de datos, ya que distribuye el procesamiento a varios núcleos de la CPU.

Para convertir un stream en paralelo, solo necesitas invocar el método parallel():

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
numeros.parallelStream()
       .forEach(n -> System.out.println(Thread.currentThread().getName() + ": " + n));

Este código distribuirá los elementos de numeros entre diferentes hilos.


6. Ejercicios Prácticos

A continuación, se presentan algunos ejercicios para poner a prueba lo aprendido. No los resolveremos aquí, pero puedes intentar implementarlos por tu cuenta.

  1. Filtrar números mayores que 10: Dada una lista de números, crea un stream que filtre aquellos mayores que 10 y los imprima.
  2. Sumar los cuadrados de los números: Dada una lista de números, crea un stream que calcule la suma de los cuadrados de cada número.
  3. Comprobar si todos los números son positivos: Dada una lista de números, crea un stream que determine si todos los números son positivos.
  4. Obtener el primer número impar: Dada una lista de números, crea un stream que encuentre el primer número impar.

7. Conclusión

La API de Streams de Java es una herramienta poderosa para procesar colecciones de manera más eficiente y legible. A través de operaciones intermedias y terminales, puedes transformar y analizar datos de forma declarativa y fluida.

Resumen:

Te animamos a continuar practicando con los ejercicios propuestos y explorar más sobre el uso de streams en situaciones del mundo real. ¡La API de Streams es una herramienta esencial para cualquier desarrollador Java!