Procesamiento de Datos con Stream API y Estructuras de Mapas en Java

La Stream API, introducida en Java 8, representa una evolución significativa en la forma en que procesamos colecciones de datos. En lugar de iteraciones imperativas manuales, los Streams permiten un enfoque declarativo y funcional para manipular conjuntos de datos de manera eficietne.

Fundamentos de Stream API

Un Stream no es una estructura de datos que almacena elementos, sino un conducto que transfiere datos desde una fuente (como una lista o un arreglo) a través de un pipeline de operaciones. El siguiente ejemplo ilustra cómo filtrar y transformar una lista de nombres de forma concisa:

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

public class AnalisisNombres {
    public static void main(String[] args) {
        List<String> empleados = List.of("Roberto", "Ana", "Ricardo", "Beatriz", "Raúl");

        // Filtrar nombres que inician con 'R' y tienen más de 4 caracteres
        List<String> filtrados = empleados.stream()
            .filter(nombre -> nombre.startsWith("R"))
            .filter(nombre -> nombre.length() > 4)
            .collect(Collectors.toList());

        System.out.println("Resultados: " + filtrados);
    }
}

Generación de Streams

Dependiendo de la fuente de datos, existen diversas formas de inicializar un flujo:

  • Colecciones: Cualquier objeto que implemente Collection puede invocar .stream().
  • Arreglos: Se utiliza el método estático Arrays.stream(miArreglo).
  • Datos Dispersos: Mediante Stream.of(val1, val2, ...).
  • Mapas: Requieren una conversión intermedia a través de keySet(), values() o entrySet().
public class CreacionStreams {
    public static void main(String[] args) {
        // Desde arreglo
        Integer[] puntajes = {10, 20, 30, 40};
        Arrays.stream(puntajes).forEach(p -> System.out.print(p + " "));

        // Desde Map
        Map<Integer, String> usuarios = Map.of(1, "Admin", 2, "Editor");
        usuarios.entrySet().stream()
                .forEach(entry -> System.out.println("ID: " + entry.getKey() + " Rol: " + entry.getValue()));
    }
}

Operaciones Intermedias

Estas operaciones transforman un Stream en otro, permiteindo el encadenamiento de métodos:

  • filter: Descarta elementos basados en un predicado.
  • sorted: Ordena los elementos (natural o mediante comparador).
  • limit y skip: Controlan el tamaño del flujo y el punto de inicio.
  • map: Transforma cada elemento aplicando una función.
  • distinct: Elimina duplicados basándose en equals().
public class OperacionesFiltro {
    public static void main(String[] args) {
        List<Integer> valores = List.of(85, 40, 75, 90, 60, 100, 55);

        valores.stream()
               .filter(v -> v >= 60)                // Solo aprobados
               .sorted(Comparator.reverseOrder())   // De mayor a menor
               .skip(1)                             // Saltar el primero
               .limit(3)                            // Tomar los 3 siguientes
               .map(v -> v + 5)                     // Bonus de puntos
               .forEach(System.out::println);
    }
}

Operaciones Terminales y Recolección

Una operación terminal cierra el Stream y produce un resultado o un efecto colateral. Ejemplos comunes son count(), forEach(), max(), min() y collect().

public class GestionInventario {
    public static void main(String[] args) {
        List<Producto> stock = List.of(
            new Producto("Laptop", 1200.0),
            new Producto("Mouse", 25.0),
            new Producto("Teclado", 45.0)
        );

        // Obtener el producto más caro
        Optional<Producto> caro = stock.stream()
                .max(Comparator.comparingDouble(Producto::getPrecio));
        
        caro.ifPresent(p -> System.out.println("Más costoso: " + p.getNombre()));

        // Convertir a un Map: Nombre -> Precio
        Map<String, Double> mapaPrecios = stock.stream()
                .collect(Collectors.toMap(Producto::getNombre, Producto::getPrecio));
    }
}

class Producto {
    private String nombre;
    private double precio;

    public Producto(String nombre, double precio) {
        this.nombre = nombre;
        this.precio = precio;
    }
    public String getNombre() { return nombre; }
    public double getPrecio() { return precio; }
}

Estructuras de Datos: Mapas

La interfaz Map<K, V> permite almacenar pares clave-valor. Es fundamental elegir la implementación correcta según la necesidad de rendimiento u ordenamiento:

  1. HashMap: Ofrece el mejor rendimiento (O(1) promedio), pero no garantiza orden.
  2. LinkedHashMap: Mantiane el orden de inserción de las claves.
  3. TreeMap: Mantiene las claves ordenadas según su orden natural o un comparador.
public class EjemploMapas {
    public static void main(String[] args) {
        Map<String, String> paises = new HashMap<>();
        paises.put("ES", "España");
        paises.put("MX", "México");
        paises.put("AR", "Argentina");

        // Recorrido eficiente con BiConsumer
        paises.forEach((codigo, nombre) -> {
            System.out.println("Código: " + codigo + ", País: " + nombre);
        });
    }
}

Parámetros Variables y la Clase Collections

Los Varargs (parámetros variables) permiten que un método acepte un número arbitrario de argumentos utilizando la sintaxis tipo... nombre. Internamente, el compilador los trata como un arreglo.

Por otro lado, la clase de utilidad java.util.Collections proporciona métodos estáticos para operar sobre colecciones, como desordenar (shuffle), ordenar (sort) o agregar múltiples elementos simultáneamente.

public class UtilidadesJava {
    public static void main(String[] args) {
        List<String> ciudades = new ArrayList<>();
        
        // Uso de Collections.addAll con varargs
        Collections.addAll(ciudades, "Madrid", "Bogotá", "Lima", "Quito");
        
        // Desordenar la lista
        Collections.shuffle(ciudades);
        System.out.println("Aleatorio: " + ciudades);
        
        // Ordenamiento personalizado
        Collections.sort(ciudades, (a, b) -> a.length() - b.length());
        System.out.println("Por longitud: " + ciudades);
    }

    // Método propio con varargs
    public static int sumar(int... numeros) {
        int total = 0;
        for (int n : numeros) total += n;
        return total;
    }
}

Etiquetas: java Stream-API collections HashMap Lambda-Expressions

Publicado el 7-28 17:15