Manipulación de Streams en Java 8: Ejemplos Prácticos

Java 8 introdujo la API de Streams, una potente herramienta para procesar colecciones de datos de manera declarativa y eficiente. A conitnuación, se presentan varios ejemplos para ilustrar su uso.

Clase de Ejemplo: Libro

Para los siguientes ejemplos, utilizaremos una clase simple que representa un libro:

import java.util.Objects;

class Libro {
   private long idLibro;
   private String titulo;
   private String autor;
   private String descripcion;

   public Libro() {}

   public Libro(long idLibro, String titulo, String autor, String descripcion) {
       this.idLibro = idLibro;
       this.titulo = titulo;
       this.autor = autor;
       this.descripcion = descripcion;
   }

   public long getIdLibro() {
       return idLibro;
   }

   public void setIdLibro(long idLibro) {
       this.idLibro = idLibro;
   }

   public String getTitulo() {
       return titulo;
   }

   public void setTitulo(String titulo) {
       this.titulo = titulo;
   }

   public String getAutor() {
       return autor;
   }

   public void setAutor(String autor) {
       this.autor = autor;
   }

   public String getDescripcion() {
       return descripcion;
   }

   public void setDescripcion(String descripcion) {
       this.descripcion = descripcion;
   }

   @Override
   public String toString() {
       return "Libro{" +
              "idLibro=" + idLibro +
              ", titulo='" + titulo + '\'' +
              ", autor='" + autor + '\'' +
              ", descripcion='" + descripcion + '\'' +
              '}';
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Libro libro = (Libro) o;
       return idLibro == libro.idLibro &&
              Objects.equals(titulo, libro.titulo) &&
              Objects.equals(autor, libro.autor) &&
              Objects.equals(descripcion, libro.descripcion);
   }

   @Override
   public int hashCode() {
       return Objects.hash(idLibro, titulo, autor, descripcion);
   }
}

Ejemplos de Uso de Streams

La siguiente clase demuestra varias operaciones comunes con Streams:

import java.util.*;
import java.util.stream.Collectors;

public class DemoStreams {

   public static void main(String[] args) {
       List<Libro> listaLibros = inicializarDatos();

       // 1. map: Transformar elementos
       // Extraer una lista de IDs de libros
       listaLibros.stream().map(Libro::getIdLibro).forEach(System.out::println);
       // Extraer una lista de títulos de libros
       listaLibros.stream().map(Libro::getTitulo).forEach(System.out::println);

       // 2. filter: Seleccionar elementos que cumplen una condición
       listaLibros.stream()
                  .filter(libro -> libro.getIdLibro() > 50)
                  .forEach(System.out::println);

       System.out.println("==Eliminar Duplicados==");
       // Añadir un elemento duplicado para demostración
       listaLibros.add(new Libro(1, "Libro1", "Autor1", "Desc1"));
       // distinct(): Elimina elementos duplicados (requiere hashCode() y equals() bien implementados)
       listaLibros.stream().distinct().forEach(System.out::println);

       System.out.println("==Ordenamiento Personalizado==");
       // sorted(): Ordena los elementos según un comparador
       // Orden descendente por ID
       listaLibros.stream()
                  .sorted((l1, l2) -> Long.compare(l2.getIdLibro(), l1.getIdLibro()))
                  .forEach(System.out::println);

       // 3. Operaciones de Coincidencia (Matching)
       boolean todosTienenTituloLibro99 = listaLibros.stream().allMatch(libro -> libro.getTitulo().equals("Libro99"));
       System.out.println("¿Todos los libros tienen el título 'Libro99'?: " + todosTienenTituloLibro99);

       boolean algunoTieneTituloLibro88 = listaLibros.stream().anyMatch(libro -> libro.getTitulo().equals("Libro88"));
       System.out.println("¿Algún libro tiene el título 'Libro88'?: " + algunoTieneTituloLibro88);

       boolean ningunoTieneTituloLibro77 = listaLibros.stream().noneMatch(libro -> libro.getTitulo().equals("Libro77"));
       System.out.println("¿Ningún libro tiene el título 'Libro77'?: " + ningunoTieneTituloLibro77);

       // 4. Obtener elementos específicos
       // max(): Encuentra el elemento máximo según un comparador
       Optional<Libro> libroMaxId = listaLibros.stream()
                                              .max(Comparator.comparingLong(Libro::getIdLibro));
       libroMaxId.ifPresent(libro -> System.out.println("Libro con ID máximo: " + libro));

       // min(): Encuentra el elemento mínimo según un comparador
       Optional<Libro> libroMinId = listaLibros.stream()
                                              .min(Comparator.comparingLong(Libro::getIdLibro));
       libroMinId.ifPresent(libro -> System.out.println("Libro con ID mínimo: " + libro));

       // findFirst(): Encuentra el primer elemento que cumple la condición
       Optional<Libro> primerLibroConTituloLibro1 = listaLibros.stream()
                                                               .filter(libro -> libro.getTitulo().equals("Libro1"))
                                                               .findFirst();
       primerLibroConTituloLibro1.ifPresent(libro -> System.out.println("Primer libro encontrado con título 'Libro1': " + libro));

       // count(): Cuenta el número total de elementos en el stream
       long totalLibros = listaLibros.stream().count();
       System.out.println("Número total de libros: " + totalLibros);

       // 5. Colecciones: Convertir Streams a otras estructuras
       // Convertir el stream a un Set (eliminando duplicados implícitamente)
       Set<Libro> setLibros = listaLibros.stream().collect(Collectors.toSet());

       // Agrupar libros por título en un Map
       Map<String, List<Libro>> mapaLibrosPorTitulo = listaLibros.stream()
                                                                     .collect(Collectors.groupingBy(Libro::getTitulo));

       // Crear una nueva lista solo con los títulos
       List<String> listaTitulos = listaLibros.stream()
                                               .map(Libro::getTitulo)
                                               .collect(Collectors.toList());

       // 6. flatMap: Aplanar colecciones anidadas
       // Ejemplo: Aplanar una lista de listas de libros en una sola lista de libros
       List<Libro> subLista1 = new ArrayList<>(listaLibros);
       List<Libro> subLista2 = new ArrayList<>(listaLibros);
       List<Libro> subLista3 = new ArrayList<>(listaLibros);

       List<List<Libro>> listaDeListasLibros = Arrays.asList(subLista1, subLista2, subLista3);

       List<Libro> librosAplanados = listaDeListasLibros.stream()
                                                      .flatMap(List::stream)
                                                      .collect(Collectors.toList());
       librosAplanados.forEach(System.out::println);
   }

   /**
    * Inicializa una lista de objetos Libro para demostración.
    * @return Una lista de objetos Libro.
    */
   public static List<Libro> inicializarDatos() {
       List<Libro> libros = new ArrayList<>(100);
       for (int i = 1; i <= 100; i++) {
           libros.add(new Libro(i, "Libro" + i, "Autor" + i, "Descripcion" + i));
       }
       return libros;
   }
}

Transformación de Listas a Map y Set

A continuación, se muestra cómo transformar una lista de objetos personalizados (RouteLine en este caso) en estructuras de Map y Set usando Streams:

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

// Suponiendo que RouteLine es una clase similar a Libro
// class RouteLine { String getCode(); String getName(); }
// Suponiendo que routeLineRepository existe y tiene un método findAll()

// List<RouteLine> lineList = routeLineRepository.findAll();

// Convertir a un Map<String, RouteLine> usando el código como clave
Map<String, RouteLine> mapaLineas = lineList.stream()
                                         .collect(Collectors.toMap(RouteLine::getCode, linea -> linea));

// Convertir a un ConcurrentMap<String, String> usando el código como clave y el nombre como valor
ConcurrentMap<String, String> mapaConcurrenteCodigosNombres = lineList.stream()
                                                                 .collect(Collectors.toConcurrentMap(RouteLine::getCode, RouteLine::getName));

// Extraer los códigos a un Set<String>
Set<String> setCodigos = lineList.stream()
                                .map(RouteLine::getCode)
                                .collect(Collectors.toSet());

Etiquetas: java Java 8 Streams api de streams programación funcional

Publicado el 9-12 07:02