1. Zliczanie elementów: count()
Gdy pracujesz z kolekcjami liczb lub obiektów, bardzo często pojawia się zadanie coś policzyć: liczbę elementów, sumę, średnią, maksimum lub minimum. Na przykład: liczba pracowników w firmie, suma wszystkich pensji, średni wiek studentów, najdroższy produkt, największe zamówienie. Z pojawieniem się Stream API wszystko stało się prostsze i bardziej czytelne.
Jak to działa
Metoda count() to operator terminalny Stream API, który zwraca liczbę elementów w strumieniu.
long count = employees.stream().count();
Jeśli chcesz policzyć tylko te elementy, które spełniają warunek — użyj filtra filter(...):
long richCount = employees.stream()
.filter(e -> e.getSalary() > 100_000)
.count();
Przykład dla naszej aplikacji:
Niech mamy listę produktów:
List<Product> products = List.of(
new Product("Mleko", 80),
new Product("Ser", 250),
new Product("Chleb", 40)
);
Policzmy, ile produktów jest droższych niż 100:
long expensiveCount = products.stream()
.filter(p -> p.getPrice() > 100)
.count();
System.out.println("Liczba drogich produktów: " + expensiveCount);
Czym jest Optional
W Javie istnieje specjalny kontener — Optional. To obiekt-opakowanie, który może zawierać wartość albo być pusty. Używa się go w sytuacjach, gdy wyniku może nie być i trzeba to wyraźnie pokazać. Na przykład metody min(), max() i average() mogą zwrócić pusty rezultat, jeśli kolekcja jest pusta. Zamiast null zwracany jest Optional.
Podstawowe metody Optional
- isPresent() / isEmpty() — sprawdzenie, czy istnieje wartość.
- orElse(wartość) — zwróć wartość lub domyślną, jeśli pusto.
- orElseThrow() — rzuć wyjątek, jeśli pusto.
- ifPresent(...) — wykonaj działanie tylko wtedy, gdy wartość jest obecna.
Przykład:
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
if (min.isPresent()) {
System.out.println("Minimalna cena: " + min.getAsInt());
} else {
System.out.println("Lista jest pusta");
}
Albo krócej:
int minValue = min.orElse(-1);
System.out.println("Minimalna cena: " + minValue);
Takie podejście pozwala unikać NullPointerException i czyni kod bezpieczniejszym.
2. Suma, średnia, minimum i maksimum dla danych liczbowych
Strumienie prymitywne: IntStream, DoubleStream, LongStream
Dla danych liczbowych Stream API oferuje specjalne strumienie: IntStream, DoubleStream, LongStream. Pozwalają one łatwo i szybko obliczać sumę, średnią, minimum i maksimum.
Konwersja do strumienia liczb
Aby uzyskać strumień liczbowy ze strumienia obiektów, użyj metod mapToInt, mapToDouble, mapToLong.
int sum = products.stream()
.mapToInt(Product::getPrice)
.sum();
System.out.println("Łączna suma cen: " + sum);
Metody
- sum() — suma wszystkich elementów.
- average() — średnia arytmetyczna (zwraca OptionalDouble).
- min(), max() — minimum i maksimum (zwracają OptionalInt/OptionalDouble/OptionalLong).
Przykłady:
// Suma wszystkich cen
int total = products.stream()
.mapToInt(Product::getPrice)
.sum();
// Średnia cena
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
// Minimalna i maksymalna cena
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
OptionalInt max = products.stream()
.mapToInt(Product::getPrice)
.max();
Jak pobrać wartość z Optional
double average = avg.orElse(0.0); // 0.0, jeśli kolekcja jest pusta
int minValue = min.orElse(-1); // -1, jeśli kolekcja jest pusta
int maxValue = max.orElse(-1); // -1, jeśli kolekcja jest pusta
Żart programisty: pusty zbiór to nie błąd, to tylko sytuacja, w której nie masz danych. Ale jeśli spróbujesz pobrać wartość z pustego Optional metodą getAsInt() — złapiesz NoSuchElementException. Zawsze używaj orElse albo isPresent()!
3. Agregacja obiektów: Collectors.summingInt, averagingInt, maxBy, minBy
Jeśli masz strumień obiektów i chcesz agregować wartości według jakiegoś kryterium, użyj specjalnych kolektorów z klasy Collectors.
summingInt, averagingInt
int totalSalary = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
double avgSalary = employees.stream()
.collect(Collectors.averagingInt(Employee::getSalary));
minBy, maxBy
Aby znaleźć obiekt z maksymalną/minimalną wartością pola:
Optional<Employee> richest = employees.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));
Optional<Employee> poorest = employees.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));
Ważne: wynik to Optional, ponieważ kolekcja może być pusta.
Przykład
Utwórzmy klasę Product:
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
Utwórzmy listę produktów:
List<Product> products = List.of(
new Product("Mleko", 80),
new Product("Ser", 250),
new Product("Chleb", 40)
);
Znajdźmy najdroższy produkt:
Optional<Product> maxProduct = products.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Product::getPrice)));
maxProduct.ifPresent(p -> System.out.println("Najdroższy produkt: " + p.getName()));
4. Łączenie z grupowaniem
Metody agregujące często łączy się z grupowaniem. Na przykład można znaleźć średnią cenę produktów według pierwszej litery nazwy:
Map<Character, Double> avgPriceByLetter = products.stream()
.collect(Collectors.groupingBy(
p -> p.getName().charAt(0),
Collectors.averagingInt(Product::getPrice)
));
System.out.println(avgPriceByLetter);
Wynik:
{M=80.0, S=250.0, C=40.0}
5. Przykłady z życia wzięte
Przykład 1: Studenci i oceny
Lista studentów i ich wyniki:
public class Student {
private final String name;
private final int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
}
List<Student> students = List.of(
new Student("Alisa", 90),
new Student("Bob", 75),
new Student("Vasya", 100)
);
Ilu studentów ma wynik powyżej 80?
long count = students.stream()
.filter(s -> s.getScore() > 80)
.count();
System.out.println("Studentów z wynikiem > 80: " + count);
Średni wynik:
double avg = students.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
System.out.println("Średni wynik: " + avg);
Najlepszy student:
students.stream()
.max(Comparator.comparingInt(Student::getScore))
.ifPresent(s -> System.out.println("Najlepszy student: " + s.getName()));
Przykład 2: Liczba unikalnych produktów
long uniqueCount = products.stream()
.map(Product::getName)
.distinct()
.count();
System.out.println("Liczba unikalnych produktów: " + uniqueCount);
6. Przydatne szczegóły
Schemat: jak działają metody agregujące
Kolekcja obiektów
│
▼
stream()
│
▼
mapToInt/Double/Long (jeśli trzeba)
│
▼
sum() / average() / min() / max() / count()
│
▼
Wynik (int, double, long, Optional)
Obsługa Optional: jak bezpiecznie pobierać wartości
Dlaczego metody zwracają Optional?
Jeśli kolekcja jest pusta (np. szukasz maksimum w pustej liście), zwracany jest pusty Optional. Jeśli od razu wywołasz getAsInt()/get(), zostanie rzucony wyjątek.
Jak postępować poprawnie:
- Używać orElse(wartość domyślna)
- Używać ifPresent(...) do działań tylko przy obecnej wartości
- Używać orElseThrow(...) do jawnego rzucenia wyjątku
Przykład:
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
System.out.println("Średnia cena: " + avg.orElse(0.0));
Kiedy używać strumieni prymitywnych, a kiedy — Collectors
- Jeśli trzeba po prostu policzyć sumę/średnią/minimum/maksimum po polu liczbowym — użyj mapToInt i odpowiedniej metody (sum, average, min, max).
- Jeśli chcesz agregować obiekty (np. znaleźć obiekt z maksymalną wartością pola), użyj kolektorów maxBy/minBy.
- Jeśli trzeba pogrupować i policzyć agregaty w grupach — użyj kombinacji groupingBy z kolektorami agregującymi.
7. Typowe błędy przy pracy z metodami agregującymi
Błąd nr 1: Optional nie został obsłużony. Nowicjusze często zapominają, że min, max, average zwracają Optional. W rezultacie przy próbie pobrania wartości z pustego kontenera pojawia się NoSuchElementException. Zawsze używaj orElse, orElseThrow lub ifPresent.
Błąd nr 2: Użycie zwykłego stream zamiast mapToInt/mapToDouble. Jeśli napiszesz stream().sum(), kompilator powie: „Takiej metody nie ma!”. Do sumy, średniej, minimum i maksimum potrzebne są strumienie prymitywne.
Błąd nr 3: Niezgodność typów przy porównaniu. Gdy szukasz maksimum/minimum wśród obiektów, używaj właściwego komparatora: Comparator.comparingInt(Employee::getSalary). W przeciwnym razie możesz dostać błąd kompilacji albo niepoprawny wynik.
Błąd nr 4: Porównywanie obiektów z null. Jeśli w kolekcji mogą występować elementy null, agregacja może rzucić NullPointerException. Lepiej wcześniej przefiltrować takie elementy albo użyć Comparator.nullsLast(...)/nullsFirst(...).
Błąd nr 5: Używanie sum/average/min/max z pustymi kolekcjami bez uwzględnienia wyniku. Jeśli kolekcja jest pusta, metoda sum() zwróci 0, a average()/min()/max() — pusty Optional. Nie zapominaj obsłużyć tego przypadku.
GO TO FULL VERSION