Volver a Java Intermedio

Introducción

Los Streams proporcionan un enfoque funcional para procesar colecciones de datos. Combinados con expresiones lambda, permiten código conciso y expresivo para filtrar, mapear y reducir datos.

Expresiones Lambda

Las lambdas son funciones anónimas que pueden tratarse como valores:

// Forma tradicional Comparator<String> comparador = new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } }; // Forma lambda Comparator<String> lambdaComparador = (a, b) -> a.length() - b.length(); // Aún más simple con referencia de método Comparator<String> refMetodo = Comparator.comparingInt(String::length);

Conceptos Básicos de Streams

Creando y usando streams:

List<String> nombres = Arrays.asList("Alicia", "Bob", "Carlos", "David"); // Filtrar y recolectar List<String> nombresLargos = nombres.stream() .filter(nombre -> nombre.length() > 4) .collect(Collectors.toList()); // Resultado: [Alicia, Carlos, David] // Transformación con map List<Integer> longitudesNombres = nombres.stream() .map(String::length) .collect(Collectors.toList()); // Resultado: [6, 3, 6, 5] // Contar elementos long cantidad = nombres.stream() .filter(nombre -> nombre.startsWith("A")) .count(); // Resultado: 1

Operaciones Comunes de Stream

Filtrado:

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> numerosPares = numeros.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); // Resultado: [2, 4, 6, 8, 10]

Mapeo:

List<String> mayusculas = nombres.stream() .map(String::toUpperCase) .collect(Collectors.toList()); // Resultado: [ALICIA, BOB, CARLOS, DAVID]

Ordenamiento:

List<String> ordenados = nombres.stream() .sorted() .collect(Collectors.toList()); // Resultado: [Alicia, Bob, Carlos, David] List<String> ordenadosPorLongitud = nombres.stream() .sorted(Comparator.comparingInt(String::length)) .collect(Collectors.toList()); // Resultado: [Bob, David, Alicia, Carlos]

Reducción:

// Sumar todos los números int suma = numeros.stream() .reduce(0, (a, b) -> a + b); // Resultado: 55 // Alternativa con Integer::sum int suma2 = numeros.stream() .reduce(0, Integer::sum); // Encontrar máximo Optional<Integer> max = numeros.stream() .max(Integer::compareTo);

FlatMap - Aplanar Colecciones Anidadas

List<List<Integer>> listaAnidada = Arrays.asList( Arrays.asList(1, 2, 3), Arrays.asList(4, 5), Arrays.asList(6, 7, 8) ); List<Integer> aplanada = listaAnidada.stream() .flatMap(List::stream) .collect(Collectors.toList()); // Resultado: [1, 2, 3, 4, 5, 6, 7, 8]

Collectors

Agrupación:

List<String> palabras = Arrays.asList("manzana", "banana", "aguacate", "mora", "cereza"); Map<Character, List<String>> agrupadasPorPrimeraLetra = palabras.stream() .collect(Collectors.groupingBy(palabra -> palabra.charAt(0))); // Resultado: {m=[manzana, mora], b=[banana], a=[aguacate], c=[cereza]}

Particionamiento:

Map<Boolean, List<Integer>> particionados = numeros.stream() .collect(Collectors.partitioningBy(n -> n % 2 == 0)); // Resultado: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}

Unión:

String unidos = nombres.stream() .collect(Collectors.joining(", ")); // Resultado: "Alicia, Bob, Carlos, David"

Streams Paralelos

Procesa grandes conjuntos de datos en paralelo:

long cantidad = listaGrande.parallelStream() .filter(item -> item.esValido()) .count();

Buenas Prácticas

  • Usa streams para procesamiento declarativo de datos
  • Prefiere referencias de métodos cuando sea posible
  • No abuses de streams para operaciones simples
  • Ten cuidado con streams paralelos (no siempre son más rápidos)
  • Evita efectos secundarios en operaciones de stream
  • Usa Optional para manejar valores faltantes de forma segura

Malas Prácticas Comunes

  • Modificar estado externo dentro de operaciones de stream
  • Usar streams para todo (a veces los bucles son más claros)
  • No considerar el rendimiento para grandes conjuntos de datos
  • Olvidar operaciones terminales (el stream no se ejecutará)

Ejemplos Prácticos

Filtrar y transformar datos de jugadores:

List<Jugador> jugadoresActivos = jugadores.stream() .filter(Jugador::estaEnLinea) .filter(j -> j.getNivel() > 10) .sorted(Comparator.comparingInt(Jugador::getPuntaje).reversed()) .collect(Collectors.toList());

Calcular estadísticas:

DoubleSummaryStatistics estadisticas = puntajes.stream() .mapToDouble(Puntaje::getValor) .summaryStatistics(); System.out.println("Promedio: " + estadisticas.getAverage()); System.out.println("Máximo: " + estadisticas.getMax()); System.out.println("Cantidad: " + estadisticas.getCount());

Filtrado y agrupación compleja:

Map<String, Long> jugadoresPorPais = jugadores.stream() .filter(j -> j.getNivel() > 20) .collect(Collectors.groupingBy( Jugador::getPais, Collectors.counting() ));

Conclusión

Los Streams y las expresiones lambda proporcionan una forma poderosa y expresiva de procesar colecciones. Reducen el código repetitivo y hacen tus intenciones más claras, conduciendo a un código más mantenible.

En el próximo capítulo, exploraremos operaciones de entrada/salida de archivos.