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. Не забывайте обрабатывать этот случай.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ