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
Czym różnią się collect() i reduce()?
reduce() – redukuje wszystkie elementy strumienia do pojedynczej wartości przez zastosowanie funkcji łączącej
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) { Listnames = 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]} } }
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) { Listnumbers = 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" } }
Kiedy używać collect() a kiedy reduce()?
Użyj collect() gdy: | Użyj reduce() gdy: |
---|---|
Chcesz zebrać elementy do kolekcji | Chcesz otrzymać pojedynczą wartość |
Potrzebujesz grupowania lub partycjonowania | Wykonujesz operacje matematyczne (suma, iloczyn) |
Transformujesz strumień w Map | Łączysz elementy w jeden (concatenation) |
Używasz gotowych Collectors | Implementujesz 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) { Listorders = 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 } }
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]
Nie zawsze. collect() jest zoptymalizowane dla kolekcji, ale reduce() może być szybsze dla prostych operacji matematycznych. Oba są zoptymalizowane pod parallel streams.
Tak! Możesz utworzyć własny Collector używając Collector.of() lub implementując interfejs Collector. To przydatne dla niestandardowych transformacji.
collect() zwróci pustą kolekcję odpowiedniego typu, reduce() bez identity zwróci Optional.empty(), a z identity zwróci wartość początkową.
Tak, ale funkcja łącząca musi być asocjacyjna i przemienna. Dla parallel streams lepiej użyć trójargumentowej wersji reduce().
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:
- Oracle – Stream.collect() Documentation
- Oracle – Stream.reduce() Documentation
- Oracle – Collectors Class Reference
A Ty które podejście preferujesz w swoich projektach – collect() czy reduce()? Podziel się doświadczeniami w komentarzach!