- 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]
- 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)