CodeGym /Cursos /JAVA 25 SELF /Métodos reduce y collect: agregación de datos

Métodos reduce y collect: agregación de datos

JAVA 25 SELF
Nivel 31, Lección 1
Disponible

1. Método reduce: una reducción universal

En programación a menudo necesitamos «reducir» una colección a un único valor final: calcular una suma, hallar un producto, concatenar cadenas, computar una métrica agregada o recopilar elementos en una nueva estructura. Antes esto se hacía manualmente con bucles y una variable acumuladora. Hoy el API de Stream ofrece formas elegantes —los métodos universales reduce y collect— que permiten escribir código de forma compacta y declarativa.

  • reduce reduce el stream a un único valor final (suma, producto, concatenación, etc.).
  • collect transforma el stream en una colección, cadena, mapa o una estructura arbitraria.

Veámoslo paso a paso.

¿Para qué sirve reduce?

reduce es un método terminal que «reduce» los elementos del stream a un único valor utilizando una función acumuladora. Mentalmente equivale a recorrer la colección acumulando el resultado en cada paso.

Firmas del método reduce

En el API de Stream hay tres variantes principales de reduce():

Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
  • accumulator: función que recibe el valor acumulado actual y el siguiente elemento, y devuelve el nuevo resultado.
  • identity: valor inicial del acumulador (por ejemplo, 0 para suma, 1 para producto).
  • combiner: se usa en streams paralelos para unir resultados intermedios.

Ejemplos de uso de reduce

Ejemplo 1: Suma de números

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

// reduce sin identity — el resultado es Optional
Optional<Integer> sum1 = numbers.stream()
    .reduce((a, b) -> a + b);

System.out.println(sum1.orElse(0)); // 15

// reduce con identity — el resultado siempre existe
int sum2 = numbers.stream()
    .reduce(0, (a, b) -> a + b);

System.out.println(sum2); // 15

Ejemplo 2: Producto de todos los números

int product = numbers.stream()
    .reduce(1, (a, b) -> a * b);
System.out.println(product); // 120

Ejemplo 3: Concatenación de cadenas

List<String> words = List.of("Java", "Stream", "API");
String phrase = words.stream()
    .reduce("", (a, b) -> a + " " + b);
System.out.println(phrase.trim()); // Java Stream API

Ejemplo 4: Búsqueda del elemento máximo

Optional<Integer> max = numbers.stream()
    .reduce(Integer::max);
max.ifPresent(System.out::println); // 5

Ejemplo 5: Suma de las longitudes de todas las cadenas

List<String> texts = List.of("gato", "perro", "elefante");
int totalLength = texts.stream()
    .map(String::length)
    .reduce(0, Integer::sum);
System.out.println(totalLength); // 17

Cómo funciona reduce

La lógica de reduce es equivalente al siguiente bucle:

T result = identity;
for (T element : collection) {
    result = accumulator.apply(result, element);
}
return result;

Si no se indica identity, el valor inicial es el primer elemento del stream y el método devuelve un Optional (puede estar vacío si el stream está vacío).

2. Método collect: transformación universal

collect es un método terminal que convierte un stream en una colección, cadena, mapa o cualquier otra estructura. Para ello se usan «colectores» (Collector) que describen el proceso de recolección. Lo más habitual es usar los que ya vienen en la clase Collectors.

Colectores más populares

  • Collectors.toList() — recopila los elementos en un List.
  • Collectors.toSet() — recopila los elementos en un Set.
  • Collectors.toMap() — recopila los elementos en un Map.
  • Collectors.joining() — une cadenas en una sola.
  • Collectors.groupingBy() — agrupa los elementos por un criterio.
  • Collectors.counting() — cuenta la cantidad de elementos.
  • Collectors.summarizingInt() — recopila estadísticas de números (suma, media, mín/máx).

Ejemplos de uso de collect

Ejemplo 1: Recolectar en una lista

List<String> names = List.of("Ana", "Boris", "Basil", "Ana");
List<String> uniqueNames = names.stream()
    .distinct()
    .collect(Collectors.toList());
System.out.println(uniqueNames); // [Ana, Boris, Basil]

Ejemplo 2: Recolectar en un conjunto (Set)

Set<String> nameSet = names.stream()
    .collect(Collectors.toSet());
System.out.println(nameSet); // [Ana, Boris, Basil] (el orden no está garantizado)

Ejemplo 3: Recolectar en una cadena

String csv = names.stream()
    .collect(Collectors.joining(", "));
System.out.println(csv); // Ana, Boris, Basil, Ana

Ejemplo 4: Recolectar en un Map

Supongamos que tenemos una clase:

public class Employee {
    private String name;
    private String department;
    public Employee(String name, String department) {
        this.name = name;
        this.department = department;
    }
    public String getName() { return name; }
    public String getDepartment() { return department; }
}

Construyamos un mapa «nombre → departamento»:

List<Employee> employees = List.of(
    new Employee("Ana", "IT"),
    new Employee("Boris", "HR"),
    new Employee("Basil", "IT")
);

Map<String, String> nameToDept = employees.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        Employee::getDepartment,
        (oldValue, newValue) -> newValue // manejo de duplicados de nombres
    ));

System.out.println(nameToDept); // {Ana=IT, Boris=HR, Basil=IT}

Ejemplo 5: Recolectar elementos únicos en un Set

Set<String> unique = names.stream()
    .collect(Collectors.toSet());
System.out.println(unique);

Ejemplo 6: Recopilar estadísticas sobre números

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
IntSummaryStatistics stats = numbers.stream()
    .collect(Collectors.summarizingInt(Integer::intValue));
System.out.println(stats.getSum());     // 15
System.out.println(stats.getAverage()); // 3.0
System.out.println(stats.getMax());     // 5
System.out.println(stats.getMin());     // 1

3. Comparación: cuándo usar reduce y cuándo collect

reduce — cuando necesitas obtener un único valor final mediante una operación binaria: suma, producto, máximo, concatenación.

collect — cuando necesitas recopilar elementos en una colección/Mapa/cadena o realizar una agregación más compleja con un Collector. Para estas tareas, collect suele ser más potente y eficiente.

Tabla: reduce vs collect

Tarea Qué usar Ejemplo
Suma de números
reduce
reduce(0, Integer::sum)
Producto
reduce
reduce(1, (a, b) -> a * b)
Recolección en List
collect
collect(Collectors.toList())
Recolección en Map
collect
collect(Collectors.toMap(...))
Agrupación
collect
collect(Collectors.groupingBy(...))
Concatenación de cadenas reduce / collect reduce("", String::concat) o Collectors.joining()

4. Tareas prácticas

Tarea 1: Encontrar la suma de las longitudes de todas las cadenas de la lista

List<String> words = List.of("gato", "perro", "elefante");

int totalLength = words.stream()
    .mapToInt(String::length)
    .sum(); // o mediante reduce: .reduce(0, Integer::sum)

System.out.println(totalLength); // 17

Tarea 2: Recopilar elementos únicos en un Set

List<String> fruits = List.of("manzana", "pera", "manzana", "naranja");

Set<String> uniqueFruits = fruits.stream()
    .collect(Collectors.toSet());

System.out.println(uniqueFruits); // [manzana, pera, naranja]

Tarea 3: Construir un Map a partir de una lista de objetos

List<Employee> employees = List.of(
    new Employee("Ana", "IT"),
    new Employee("Boris", "HR"),
    new Employee("Basil", "IT")
);

Map<String, String> nameToDept = employees.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        Employee::getDepartment,
        (oldValue, newValue) -> newValue // si los nombres coinciden
    ));

System.out.println(nameToDept);

Tarea 4: Recolectar todos los nombres separados por comas

String allNames = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.joining(", "));

System.out.println(allNames); // Ana, Boris, Basil

5. Particularidades de implementación y matices

Optional y reduce

Si se usa reduce sin identity, el resultado es un Optional. Esto es seguro: si el stream está vacío, el resultado también lo está. No olvides gestionarlo correctamente: ifPresent(...), orElse(...), orElseThrow(...).

Optional<Integer> max = numbers.stream().reduce(Integer::max);
max.ifPresent(System.out::println);

Colectores propios: si necesitas algo extremo

Puedes escribir tu propio Collector si los estándar no son suficientes. Pero para el 99% de los casos bastan los que ofrece Collectors.

Colectores y streams paralelos

Los colectores de Collectors están diseñados para funcionar correctamente con parallelStream(). No añadas elementos manualmente a una colección mutables compartida dentro de forEach en un stream paralelo — te encontrarás con condiciones de carrera.

6. Errores típicos al trabajar con reduce y collect

Error n.º 1: No compruebas Optional tras reduce. Si el stream está vacío, reduce sin identity devuelve un Optional vacío. Llamar a get() provocará un NoSuchElementException. Usa ifPresent, orElse u orElseThrow.

Error n.º 2: Intentas recopilar una colección con reduce. Se puede hacer, pero collect está pensado para ello y suele ser mejor y más rápido:

// ¡Ineficiente!
List<String> list = stream.reduce(
    new ArrayList<>(),
    (acc, elem) -> { acc.add(elem); return acc; },
    (acc1, acc2) -> { acc1.addAll(acc2); return acc1; }
);
// Mejor así:
List<String> list2 = stream.collect(Collectors.toList());

Error n.º 3: No manejas claves duplicadas en toMap. Si las claves coinciden, habrá una excepción. Añade el tercer argumento en toMap para resolver los conflictos.

Error n.º 4: Usas colecciones mutables en streams paralelos sin sincronización. En collect usa los colectores estándar: funcionan correctamente en modo paralelo. No hagas list.add() en forEach sobre un parallelStream().

Error n.º 5: Confundes reduce y collect para tareas complejas. reduce es para agregaciones simples (suma, máximo). collect es para recolectar en colecciones, agrupar, construir Map y realizar agregaciones complejas.

1
Tarea
JAVA 25 SELF, nivel 31, lección 1
Bloqueada
Búsqueda de la temperatura máxima de la semana 🌡️
Búsqueda de la temperatura máxima de la semana 🌡️
1
Tarea
JAVA 25 SELF, nivel 31, lección 1
Bloqueada
Creación de un hechizo mágico 📜
Creación de un hechizo mágico 📜
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION