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. Не забувайте обробляти цей випадок.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ