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());