CodeGym /Kursy /JAVA 25 SELF /Metody sum, count, average, max, min w Stream API

Metody sum, count, average, max, min w Stream API

JAVA 25 SELF
Poziom 31 , Lekcja 0
Dostępny

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.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION