Stream API – collect vs reduce: Kiedy użyć którego?

TL;DR: collect() używasz do zbierania elementów do kolekcji (List, Set, Map), reduce() do redukowania strumienia do pojedynczej wartości (suma, maksimum, łączenie stringów). collect() jest bardziej elastyczny, reduce() bardziej matematyczny.

Dlaczego różnica między collect() a reduce() jest ważna?

Wyobraź sobie, że analizujesz dane sprzedażowe – czasem chcesz zebrać wszystkie zamówienia z danego miesiąca do listy (collect), a czasem obliczyć całkowitą wartość sprzedaży (reduce).

Wybór właściwej metody terminacji strumienia wpływa na czytelność kodu, wydajność i możliwość równoległego przetwarzania. Niewłaściwy wybór może prowadzić do skomplikowanego, trudnego w utrzymaniu kodu.

Co się nauczysz:

  • Kiedy używać collect() a kiedy reduce() w Stream API
  • Jak działają wbudowane Collectors (toList, groupingBy, summingInt)
  • Różne warianty reduce() i ich zastosowania
  • Praktyczne przykłady z życia programisty
  • Pułapki i najlepsze praktyki obu metod
Wymagania wstępne: Podstawowa znajomość Java 8 Stream API, lambdy i method references

Czym różnią się collect() i reduce()?

collect() – zbiera elementy strumienia do kolekcji lub innej struktury danych używając Collector

reduce() – redukuje wszystkie elementy strumienia do pojedynczej wartości przez zastosowanie funkcji łączącej

Analogia: collect() to jak sortowanie ubrań do różnych szaf (grupowanie do kolekcji), reduce() to jak pakowanie wszystkich ubrań do jednej walizkи (łączenie w jedno).

collect() – Zbieranie do kolekcji

collect() służy głównie do transformacji strumienia w kolekcję. Używa gotowych lub własnych Collectorów:

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

public class CollectExample {
    public static void main(String[] args) {
        List names = Arrays.asList(
            "Anna", "Piotr", "Katarzyna", "Jan", "Alicja"
        );
        
        // Zbieranie do listy - filtrowanie imion zaczynających się na 'A'
        List namesWithA = names.stream()
            .filter(name -> name.startsWith("A"))
            .collect(Collectors.toList());
        System.out.println("Imiona na A: " + namesWithA); // [Anna, Alicja]
        
        // Zbieranie do Set - unikalne długości imion
        Set nameLengths = names.stream()
            .map(String::length)
            .collect(Collectors.toSet());
        System.out.println("Długości imion: " + nameLengths); // [3, 5, 9]
        
        // Grupowanie po długości imiona
        Map> groupedByLength = names.stream()
            .collect(Collectors.groupingBy(String::length));
        System.out.println("Pogrupowane: " + groupedByLength);
        // {3=[Jan], 5=[Anna, Piotr], 6=[Alicja], 9=[Katarzyna]}
    }
}
Pro tip: W Java 8 (2018) używamy Collectors.toList(). W nowszych wersjach będzie dostępne toList() jako metoda domyślna, ale na razie trzymamy się tego standardu.

reduce() – Redukowanie do pojedynczej wartości

reduce() łączy wszystkie elementy strumienia w jedną wartość używając funkcji dwuargumentowej:

import java.util.*;

public class ReduceExample {
    public static void main(String[] args) {
        List numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // Suma wszystkich liczb
        int sum = numbers.stream()
            .reduce(0, (a, b) -> a + b);
        System.out.println("Suma: " + sum); // 15
        
        // Alternatywnie z method reference
        int sumRef = numbers.stream()
            .reduce(0, Integer::sum);
        System.out.println("Suma (method ref): " + sumRef); // 15
        
        // Znalezienie maksimum
        Optional max = numbers.stream()
            .reduce(Integer::max);
        System.out.println("Max: " + max.get()); // 5
        
        // Łączenie stringów
        List words = Arrays.asList("Java", "Stream", "API");
        String combined = words.stream()
            .reduce("", (a, b) -> a + " " + b);
        System.out.println("Połączone: " + combined.trim()); // "Java Stream API"
    }
}

reduce() ma trzy warianty: z wartością początkową (identity), bez identity (zwraca Optional), oraz trójargumentowy dla parallel streams.

Kiedy używać collect() a kiedy reduce()?

Użyj collect() gdy:Użyj reduce() gdy:
Chcesz zebrać elementy do kolekcjiChcesz otrzymać pojedynczą wartość
Potrzebujesz grupowania lub partycjonowaniaWykonujesz operacje matematyczne (suma, iloczyn)
Transformujesz strumień w MapŁączysz elementy w jeden (concatenation)
Używasz gotowych CollectorsImplementujesz własną logikę łączenia
Potrzebujesz statystyk (counting, averaging)Szukasz minimum/maximum z własną logiką

Praktyczne przykłady z życia

Scenario 1: Analiza zamówień e-commerce

public class Order {
    private String product;
    private double price;
    private String category;
    
    // konstruktor, gettery, settery...
    public Order(String product, double price, String category) {
        this.product = product;
        this.price = price;
        this.category = category;
    }
    
    public String getProduct() { return product; }
    public double getPrice() { return price; }
    public String getCategory() { return category; }
}

public class ECommerceAnalysis {
    public static void main(String[] args) {
        List orders = Arrays.asList(
            new Order("Laptop", 2500.0, "Electronics"),
            new Order("Mouse", 50.0, "Electronics"),
            new Order("Book", 30.0, "Books"),
            new Order("Keyboard", 120.0, "Electronics")
        );
        
        // collect() - grupowanie zamówień po kategorii
        Map> ordersByCategory = orders.stream()
            .collect(Collectors.groupingBy(Order::getCategory));
        
        // reduce() - całkowita wartość zamówień
        double totalValue = orders.stream()
            .mapToDouble(Order::getPrice)
            .reduce(0.0, Double::sum);
        
        System.out.println("Zamówienia po kategorii: " + ordersByCategory.keySet());
        System.out.println("Całkowita wartość: " + totalValue); // 2700.0
    }
}

Pułapka: Nie używaj reduce() do budowania kolekcji! Kod .reduce(new ArrayList<>(), (list, item) -> {list.add(item); return list;}) jest niepoprawny i nieefektywny.

Zaawansowane Collectors

collect() oferuje potężne możliwości przez wbudowane Collectors:

// Partycjonowanie (podział na dwie grupy)
Map> expensiveOrders = orders.stream()
    .collect(Collectors.partitioningBy(order -> order.getPrice() > 100));

// Statystyki grupowe
Map avgPriceByCategory = orders.stream()
    .collect(Collectors.groupingBy(
        Order::getCategory,
        Collectors.averagingDouble(Order::getPrice)
    ));

// Łączenie stringów (alternatywa dla reduce)
String productNames = orders.stream()
    .map(Order::getProduct)
    .collect(Collectors.joining(", ", ", "[", "]"));
System.out.println(productNames); // [Laptop, Mouse, Book, Keyboard]
Typowy błąd początkujących: Mylenie reduce() z collect(). Jeśli wynik ma być kolekcją, użyj collect()!
Czy collect() jest zawsze wydajniejsze od reduce()?

Nie zawsze. collect() jest zoptymalizowane dla kolekcji, ale reduce() może być szybsze dla prostych operacji matematycznych. Oba są zoptymalizowane pod parallel streams.

Czy mogę używać własnych Collectors?

Tak! Możesz utworzyć własny Collector używając Collector.of() lub implementując interfejs Collector. To przydatne dla niestandardowych transformacji.

Co się stanie gdy strumień będzie pusty?

collect() zwróci pustą kolekcję odpowiedniego typu, reduce() bez identity zwróci Optional.empty(), a z identity zwróci wartość początkową.

Czy reduce() działa z parallel streams?

Tak, ale funkcja łącząca musi być asocjacyjna i przemienna. Dla parallel streams lepiej użyć trójargumentowej wersji reduce().

Które podejście jest bardziej czytelne?

collect() z gotowymi Collectors jest zwykle bardziej czytelne. reduce() lepiej sprawdza się przy prostych operacjach matematycznych gdzie logika jest oczywista.

🚀 Zadanie dla Ciebie

Stwórz klasę Student z polami: name, grade, subject. Następnie dla listy studentów:

  • Użyj collect() aby pogrupować studentów po przedmiocie
  • Użyj reduce() aby znaleźć najwyższą ocenę
  • Porównaj wydajność obu podejść dla dużej listy (10000+ elementów)

Przydatne zasoby:

A Ty które podejście preferujesz w swoich projektach – collect() czy reduce()? Podziel się doświadczeniami w komentarzach!

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry