Manejo de Streams Numéricos y Ejemplos Prácticos con la API de Streams de Java

  1. Optimización con Streams Numéricos

La API de Streams de Java 8 ofrece tipos especializados como IntStream, LongStream y DoubleStream, conocidos colectivamente como Streams Numéricos. Estos streams están diseñados para operar con tipos primitivos (int, long, double respectivamente), lo que resulta en una significativa mejora en la eficiencia de memoria y rendimiento en comparación con sus equivalentes de objetos (Stream<Integer>, Stream<Long>, Stream<Double>).

El principle beneficio es evitar el "autoboxing" y "unboxing" constante que ocurre cuando se manejan tipos envolventes (Integer, Long, Double). Los tipos primitivos requieren menos memoria y las operaciones sobre ellos son generalmente más rápidas. Se recomienda utilizar Streams Numéricos siempre que las operaciones impliquen exclusivamente valores numéricos.

Es posible convertir entre Stream<T> y Streams Numéricos. Métodos como mapToInt, mapToLong y mapToDouble transforman un stream de objetos en su contraparte numérica. Para revertir esta transformación, se utilizan los métodos boxed() o mapToObj().

Ejemplo de Uso de Streams Numéricoss

Consideremos un escenario donde necesitamos procesar una lista de números. A continuación, se muestra cómo se pueden utilizar los Streams Numéricos para una suma eficiente y la búsqueda de tripletas pitagóricas.

package com.ejemplo.streams;

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class UsoStreamsNumericos {

    public static void main(String[] args) {
        List<Integer> numerosWrapper = Arrays.asList(10, 20, 30, 40, 50, 60);

        // Sumar elementos mayores que 35 utilizando Stream<Integer>
        // Requiere autoboxing/unboxing, lo que puede ser menos eficiente.
        int sumaTradicional = numerosWrapper.stream()
                                            .filter(num -> num > 35)
                                            .reduce(0, Integer::sum);
        System.out.println("Suma de números > 35 (Stream<Integer>): " + sumaTradicional);

        // Convertir Stream<Integer> a IntStream para operaciones numéricas optimizadas
        IntStream flujoPrimitivo = numerosWrapper.stream()
                                                  .mapToInt(Integer::intValue);

        // Sumar elementos mayores que 35 usando IntStream
        // Más eficiente ya que opera directamente sobre tipos primitivos.
        int sumaOptimizada = flujoPrimitivo.filter(num -> num > 35)
                                           .sum();
        System.out.println("Suma de números > 35 (IntStream): " + sumaOptimizada);

        // Reconvertir un IntStream a Stream<Integer> (boxing)
        IntStream otroFlujoPrimitivo = IntStream.rangeClosed(1, 5); // Genera 1, 2, 3, 4, 5
        Stream<Integer> flujoBoxed = otroFlujoPrimitivo.boxed();
        System.out.println("Elementos de flujoBoxed: " + flujoBoxed.collect(java.util.stream.Collectors.toList()));

        System.out.println("\n--- Búsqueda de Tripletas Pitagóricas ---");
        // Encontrar tripletas pitagóricas (a, b, c) donde a=12 y b está en el rango [1, 50]
        // Una tripleta pitagórica cumple a*a + b*b = c*c.
        int valorA = 12; // Un lado fijo del triángulo rectángulo
        IntStream.rangeClosed(1, 50) // Rango para el segundo cateto 'b'
                 .filter(valB -> {
                     double sumaCuadrados = (double)valorA * valorA + (double)valB * valB;
                     double raiz = Math.sqrt(sumaCuadrados);
                     // Comprobar si la raíz es un número entero (sin parte decimal)
                     return raiz == Math.floor(raiz);
                 })
                 .mapToObj(valB -> new int[]{valorA, valB, (int) Math.sqrt(valorA * valorA + valB * valB)})
                 .forEach(tripl -> System.out.println("Tripl. Pitagórica: [" + tripl[0] + ", " + tripl[1] + ", " + tripl[2] + "]"));
    }
}

Salida del código:

Suma de números > 35 (Stream<Integer>): 150
Suma de números > 35 (IntStream): 150
Elementos de flujoBoxed: [1, 2, 3, 4, 5]

--- Búsqueda de Tripletas Pitagóricas ---
Tripl. Pitagórica: [12, 5, 13]
Tripl. Pitagórica: [12, 9, 15]
Tripl. Pitagórica: [12, 16, 20]
Tripl. Pitagórica: [12, 35, 37]

  1. Ejercicios Completos con la API de Streams

La API de Streams de Java proporciona una forma potente y declarativa de procesar colecciones de datos. A continuación, exploraremos varios ejemplos prácticos utilizando objetos de Transaccion y Operador para demostrar la versatilidad de los streams.

Modelos de Datos

Primero, definimos las clases Operador y Transaccion que utilizaremos para nuestras operaciones. Estas clases ecnapsulan la información relevante sobre los participantes y sus movimientos financieros.

// Archivo: Operador.java
package com.ejemplo.streams.modelos;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * Representa a un operador o comerciante.
 */
@AllArgsConstructor
@Data
public class Operador {
    private final String nombre;
    private final String ciudad;
}

// Archivo: Transaccion.java
package com.ejemplo.streams.modelos;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * Representa una transacción financiera.
 */
@AllArgsConstructor
@Data
public class Transaccion {
    private final Operador operador;
    private final int anio;
    private final int valor;
}

Casos de Uso Prácticos con Streams

Ahora, implementaremos una serie de consultas utilizando la API de Streams sobre una lista de transacciones.

package com.ejemplo.streams;

import com.ejemplo.streams.modelos.Operador;
import com.ejemplo.streams.modelos.Transaccion;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class GestionTransaccionesStream {

    public static void main(String[] args) {
        // Inicialización de operadores
        Operador rafael = new Operador("Rafael", "Cambridge");
        Operador marta = new Operador("Marta", "Milan");
        Operador andres = new Operador("Andres", "Cambridge");
        Operador beatriz = new Operador("Beatriz", "London");

        // Lista de transacciones de ejemplo
        List<Transaccion> listaTransacciones = Arrays.asList(
                new Transaccion(beatriz, 2011, 450),
                new Transaccion(rafael, 2012, 1200),
                new Transaccion(rafael, 2011, 550),
                new Transaccion(marta, 2012, 800),
                new Transaccion(marta, 2012, 750),
                new Transaccion(andres, 2011, 900)
        );

        System.out.println("--- Consultas de Transacciones con Streams ---");

        // 1. Obtener todas las transacciones del año 2011 y ordenarlas por su valor.
        List<Transaccion> transacciones2011Ordenadas = listaTransacciones.stream()
                                                                       .filter(t -> t.getAnio() == 2011)
                                                                       .sorted(Comparator.comparingInt(Transaccion::getValor))
                                                                       .collect(Collectors.toList());
        System.out.println("1. Transacciones de 2011 (ordenadas por valor): " + transacciones2011Ordenadas);

        // 2. ¿Cuáles son las ciudades únicas donde trabajan los operadores?
        List<String> ciudadesUnicas = listaTransacciones.stream()
                                                       .map(t -> t.getOperador().getCiudad())
                                                       .distinct()
                                                       .collect(Collectors.toList());
        System.out.println("2. Ciudades únicas de los operadores: " + ciudadesUnicas);

        // 3. Encontrar todos los operadores de Cambridge y ordenarlos por nombre.
        List<Operador> operadoresCambridgeOrdenados = listaTransacciones.stream()
                                                                      .map(Transaccion::getOperador) // Extrae el objeto Operador de cada Transaccion
                                                                      .filter(op -> op.getCiudad().equals("Cambridge"))
                                                                      .distinct() // Elimina operadores duplicados
                                                                      .sorted(Comparator.comparing(Operador::getNombre))
                                                                      .collect(Collectors.toList());
        System.out.println("3. Operadores de Cambridge (ordenados por nombre): " + operadoresCambridgeOrdenados);

        // 4. Retornar una cadena con los nombres de todos los operadores, ordenados alfabéticamente.
        String nombresOperadoresOrdenados = listaTransacciones.stream()
                                                               .map(t -> t.getOperador().getNombre())
                                                               .distinct()
                                                               .sorted()
                                                               .collect(Collectors.joining(", ")); // Unir los nombres con coma y espacio
        System.out.println("4. Nombres de operadores (ordenados): " + nombresOperadoresOrdenados);

        // 5. ¿Hay algún operador basado en Milán?
        boolean hayOperadorMilan = listaTransacciones.stream()
                                                    .anyMatch(t -> t.getOperador().getCiudad().equals("Milan"));
        System.out.println("5. ¿Hay algún operador de Milán? " + hayOperadorMilan);

        // 6. Imprimir la suma total de los valores de las transacciones realizadas por operadores de Cambridge.
        int totalValorCambridge = listaTransacciones.stream()
                                                    .filter(t -> t.getOperador().getCiudad().equals("Cambridge"))
                                                    .mapToInt(Transaccion::getValor) // Convertir a IntStream para usar sum()
                                                    .sum();
        System.out.println("6. Valor total de transacciones de Cambridge: " + totalValorCambridge);

        // 7. Encontrar el valor más alto de todas las transacciones.
        Optional<Integer> valorMaximo = listaTransacciones.stream()
                                                           .map(Transaccion::getValor)
                                                           .reduce(Integer::max); // Usar el método de referencia Integer::max
        valorMaximo.ifPresent(max -> System.out.println("7. Valor máximo de transacción: " + max));

        // 8. Encontrar la transacción con el valor más bajo.
        Optional<Transaccion> transaccionMinima = listaTransacciones.stream()
                                                                    .min(Comparator.comparingInt(Transaccion::getValor));
        transaccionMinima.ifPresent(min -> System.out.println("8. Transacción con el valor más bajo: " + min));
    }
}

Salida del código:

--- Consultas de Transacciones con Streams ---
1. Transacciones de 2011 (ordenadas por valor): [Transaccion(operador=Operador(nombre=Beatriz, ciudad=London), anio=2011, valor=450), Transaccion(operador=Operador(nombre=Rafael, ciudad=Cambridge), anio=2011, valor=550), Transaccion(operador=Operador(nombre=Andres, ciudad=Cambridge), anio=2011, valor=900)]
2. Ciudades únicas de los operadores: [London, Cambridge, Milan]
3. Operadores de Cambridge (ordenados por nombre): [Operador(nombre=Andres, ciudad=Cambridge), Operador(nombre=Rafael, ciudad=Cambridge)]
4. Nombres de operadores (ordenados): Andres, Beatriz, Marta, Rafael
5. ¿Hay algún operador de Milán? true
6. Valor total de transacciones de Cambridge: 1450
7. Valor máximo de transacción: 1200
8. Transacción con el valor más bajo: Transaccion(operador=Operador(nombre=Beatriz, ciudad=London), anio=2011, valor=450)

Etiquetas: java Streams IntStream StreamAPI LambdaExpressions

Publicado el 7-5 03:47