JavaRush /Курси /JAVA 25 SELF /Методи sum, count, average, max, min у Stream API

Методи sum, count, average, max, min у Stream API

JAVA 25 SELF
Рівень 31 , Лекція 0
Відкрита

1. Підрахунок елементів: count()

Коли ви працюєте з колекціями чисел або обʼєктів, майже завжди виникає завдання щось порахувати: кількість елементів, суму, середнє, максимум або мінімум. Наприклад, кількість співробітників у компанії, суму всіх зарплат, середній вік студентів, найдорожчий продукт, максимальне замовлення. З появою Stream API усе стало простіше й виразніше.

Як це працює

Метод count() — термінальний оператор Stream API, який повертає кількість елементів у потоці.

long count = employees.stream().count();

Якщо потрібно порахувати лише ті елементи, що відповідають умові, — використовуйте фільтр filter(...):

long richCount = employees.stream()
    .filter(e -> e.getSalary() > 100_000)
    .count();

Приклад для нашого застосунку:

Нехай є список продуктів:

List<Product> products = List.of(
    new Product("Молоко", 80),
    new Product("Сир", 250),
    new Product("Хліб", 40)
);

Порахуємо, скільки продуктів коштують понад 100:

long expensiveCount = products.stream()
    .filter(p -> p.getPrice() > 100)
    .count();

System.out.println("Кількість дорогих продуктів: " + expensiveCount);

Що таке Optional

У Java є спеціальний контейнер — Optional. Це обʼєкт-обгортка, який може містити значення або бути порожнім. Він використовується у випадках, коли результату може не бути, і це потрібно явно показати. Наприклад, методи min(), max() і average() можуть повернути порожній результат, якщо колекція порожня. Замість null повертається Optional.

Основні методи Optional

  • isPresent() / isEmpty() — перевірка на наявність значення.
  • orElse(значення) — повернути наявне значення або значення за замовчуванням, якщо контейнер порожній.
  • orElseThrow() — згенерувати виняток, якщо контейнер порожній.
  • ifPresent(...) — виконати дію лише за наявності значення.

Приклад:

OptionalInt min = products.stream()
    .mapToInt(Product::getPrice)
    .min();

if (min.isPresent()) {
    System.out.println("Мінімальна ціна: " + min.getAsInt());
} else {
    System.out.println("Список порожній");
}

Або коротше так:

int minValue = min.orElse(-1);
System.out.println("Мінімальна ціна: " + minValue);

Такий підхід дозволяє уникати NullPointerException і робить код безпечнішим.

2. Сума, середнє, мінімум і максимум для числових даних

Примітивні потоки: IntStream, DoubleStream, LongStream

Для числових даних Stream API пропонує спеціальні потоки: IntStream, DoubleStream, LongStream. Вони дозволяють легко та швидко обчислювати суму, середнє, мінімум і максимум.

Перетворення на числовий потік

Щоб отримати числовий потік із потоку обʼєктів, використовуйте методи mapToInt, mapToDouble, mapToLong.

int sum = products.stream()
    .mapToInt(Product::getPrice)
    .sum();
System.out.println("Загальна сума цін: " + sum);

Методи

  • sum() — сума всіх елементів.
  • average() — середнє арифметичне (повертає OptionalDouble).
  • min(), max() — мінімум і максимум (повертають OptionalInt/OptionalDouble/OptionalLong).

Приклади:

// Сума всіх цін
int total = products.stream()
    .mapToInt(Product::getPrice)
    .sum();

// Середня ціна
OptionalDouble avg = products.stream()
    .mapToInt(Product::getPrice)
    .average();

// Мінімальна та максимальна ціна
OptionalInt min = products.stream()
    .mapToInt(Product::getPrice)
    .min();

OptionalInt max = products.stream()
    .mapToInt(Product::getPrice)
    .max();

Як отримати значення з Optional

double average = avg.orElse(0.0); // 0.0, якщо колекція порожня
int minValue = min.orElse(-1);    // -1, якщо колекція порожня
int maxValue = max.orElse(-1);    // -1, якщо колекція порожня

Жарт програміста: порожній список — це не помилка, це просто ситуація, коли у вас немає даних. Але якщо ви спробуєте взяти значення з порожнього Optional методом getAsInt(), спіймаєте NoSuchElementException. Завжди використовуйте orElse або перевірку isPresent()!

3. Агрегація обʼєктів: Collectors.summingInt, averagingInt, maxBy, minBy

Якщо маєте потік обʼєктів і хочете агрегувати значення за певною ознакою, використовуйте спеціальні колектори з класу Collectors.

summingInt, averagingInt

int totalSalary = employees.stream()
    .collect(Collectors.summingInt(Employee::getSalary));

double avgSalary = employees.stream()
    .collect(Collectors.averagingInt(Employee::getSalary));

minBy, maxBy

Щоб знайти обʼєкт із максимальним/мінімальним значенням поля:

Optional<Employee> richest = employees.stream()
    .collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));

Optional<Employee> poorest = employees.stream()
    .collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));

Важливо: результат — Optional, адже колекція може бути порожньою.

Приклад

Створимо клас 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; }
}

Створимо список продуктів:

List<Product> products = List.of(
    new Product("Молоко", 80),
    new Product("Сир", 250),
    new Product("Хліб", 40)
);

Знайдемо найдорожчий продукт:

Optional<Product> maxProduct = products.stream()
    .collect(Collectors.maxBy(Comparator.comparingInt(Product::getPrice)));

maxProduct.ifPresent(p -> System.out.println("Найдорожчий продукт: " + p.getName()));

4. Комбінування з групуванням

Агрегаційні методи часто поєднуються з групуванням. Наприклад, можна знайти середню ціну продуктів за першою літерою назви:

Map<Character, Double> avgPriceByLetter = products.stream()
    .collect(Collectors.groupingBy(
        p -> p.getName().charAt(0),
        Collectors.averagingInt(Product::getPrice)
    ));

System.out.println(avgPriceByLetter);

Результат:

{М=80.0, С=250.0, Х=40.0}

5. Приклади з реального життя

Приклад 1: Студенти та оцінки

Список студентів і їхні бали:

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("Аліса", 90),
    new Student("Боб", 75),
    new Student("Вася", 100)
);

Скільки студентів із балом понад 80?

long count = students.stream()
    .filter(s -> s.getScore() > 80)
    .count();
System.out.println("Студентів із балом > 80: " + count);

Середній бал:

double avg = students.stream()
    .mapToInt(Student::getScore)
    .average()
    .orElse(0.0);
System.out.println("Середній бал: " + avg);

Найуспішніший студент:

students.stream()
    .max(Comparator.comparingInt(Student::getScore))
    .ifPresent(s -> System.out.println("Найкращий студент: " + s.getName()));

Приклад 2: Кількість унікальних товарів

long uniqueCount = products.stream()
    .map(Product::getName)
    .distinct()
    .count();
System.out.println("Унікальних товарів: " + uniqueCount);

6. Корисні нюанси

Візуальна схема: як працюють агрегаційні методи

Колекція обʼєктів
   │
   ▼
stream()
   │
   ▼
mapToInt/Double/Long (за потреби)
   │
   ▼
sum() / average() / min() / max() / count()
   │
   ▼
Результат (int, double, long, Optional)

Обробка Optional: як безпечно отримувати значення

Чому методи повертають Optional?
Якщо колекція порожня (наприклад, ви шукаєте максимум серед порожнього списку), повертається порожній Optional. Якщо одразу викликати getAsInt()/get(), буде згенеровано виняток.

Як правильно:

  • Використовувати orElse(значення за замовчуванням)
  • Використовувати ifPresent(...) для дій лише за наявності значення
  • Використовувати orElseThrow(...) для явного генерування винятку

Приклад:

OptionalDouble avg = products.stream()
    .mapToInt(Product::getPrice)
    .average();

System.out.println("Середня ціна: " + avg.orElse(0.0));

Коли використовувати примітивні потоки, а коли — Collectors

  • Якщо потрібно просто порахувати суму/середнє/мінімум/максимум за числовим полем — використовуйте mapToInt і відповідний метод (sum, average, min, max).
  • Якщо хочете агрегувати обʼєкти (наприклад, знайти обʼєкт із максимальним полем), використовуйте колектори maxBy/minBy.
  • Якщо потрібно згрупувати й порахувати агрегати за групами — використовуйте комбінацію groupingBy з агрегаційними колекторами.

7. Типові помилки під час роботи з агрегаційними методами

Помилка № 1: Optional не оброблено. Часто новачки забувають, що min, max, average повертають Optional. У результаті під час спроби отримати значення з порожнього контейнера виникає NoSuchElementException. Завжди використовуйте orElse, orElseThrow або ifPresent.

Помилка № 2: Використання звичайного stream замість mapToInt/mapToDouble. Якщо ви пишете stream().sum(), компілятор скаже: «Такого методу немає!». Для суми, середнього, мінімуму та максимуму потрібні примітивні потоки.

Помилка № 3: Невідповідність типів під час порівняння. Коли шукаєте максимум/мінімум серед обʼєктів, використовуйте правильний компаратор: Comparator.comparingInt(Employee::getSalary). Інакше можете отримати помилку компіляції або некоректний результат.

Помилка № 4: Порівняння обʼєктів із null. Якщо в колекції можуть бути null-елементи, агрегація може кидати NullPointerException. Краще попередньо фільтрувати такі елементи або використовувати Comparator.nullsLast(...)/nullsFirst(...).

Помилка № 5: Використання sum/average/min/max із порожніми колекціями без урахування результату. Якщо колекція порожня, метод sum() поверне 0, а average()/min()/max() — порожній Optional. Не забувайте обробляти цей випадок.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ