ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

Stream API: filter, reduce, groupingBy и построение отчётов

Автор: · Обновлено

После фильтрации и преобразования stream нужно превратить в полезный результат: сумму, таблицу по группам, список или ответ на условие. reduce и collect завершают обработку, но выражают разные способы накопления результата.

Сначала сформулируйте форму результата

Если нужен один скаляр, например сумма минут, удобно использовать числовой stream и sum. Если нужен контейнер, используйте collector: он описывает создание, накопление и объединение результата. groupingBy классифицирует элементы по ключу, а downstream collector определяет, что хранить в каждой группе. Сборка результата через collector.

Например, «список задач по отделам» и «суммарные минуты по отделам» имеют одинаковую классификацию, но разные значения Map: в первом случае списки Task, во втором числа. Не создавайте промежуточную карту списков, если весь последующий код лишь суммирует каждый из них.

Полный отчёт по задачам

Сохраните TaskReport.java. В record названы два компонента; методы team() и minutes() создаются для доступа к ним. Входные данные в этом примере заранее проверены и не содержат null.

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;

public class TaskReport {
    record Task(String team, int minutes) {}

    public static void main(String[] args) {
        List<Task> tasks = List.of(
                new Task("core", 40), new Task("web", 20),
                new Task("core", 15), new Task("web", 10));

        Map<String, Integer> significant = tasks.stream()
                .filter(task -> task.minutes() >= 15)
                .collect(Collectors.groupingBy(Task::team, TreeMap::new,
                        Collectors.summingInt(Task::minutes)));
        int total = tasks.stream().mapToInt(Task::minutes).sum();
        int reduced = tasks.stream().map(Task::minutes).reduce(0, Integer::sum);
        Map<String, Long> counts = tasks.stream().map(Task::team)
                .collect(Collectors.groupingBy(Function.identity(), TreeMap::new,
                        Collectors.counting()));

        System.out.println(significant);
        System.out.println("total=" + total + ", reduced=" + reduced);
        System.out.println(counts);
        System.out.println(tasks.stream().anyMatch(task -> task.minutes() >= 60));
    }
}

Команды: javac -encoding UTF-8 --release 25 TaskReport.java, затем java TaskReport. Вывод:

{core=55, web=20}
total=85, reduced=85
{core=2, web=2}
false

Task::team означает вызов метода получения отдела для каждого элемента. Function.identity() возвращает вход без изменения: после map(Task::team) в потоке уже строки. TreeMap::new выбран, чтобы порядок ключей отчёта был определён; обычный groupingBy не обещает конкретного класса Map. Фабрики Collectors.

Что требуется от reduce

Для reduce(0, Integer::sum) ноль — нейтральное значение: добавление нуля не меняет частичную сумму. Операция объединения должна быть ассоциативной в используемой модели вычисления, чтобы группировка элементов не меняла ответ. Вычитание и произвольная запись во внешний счётчик этим требованиям не соответствуют. Для чисел с плавающей точкой отдельно учитывайте округление. Условия редукции.

Не используйте один изменяемый ArrayList как «нейтральное значение» reduce, добавляя в него элементы. Такой код смешивает неизменяемое описание накопленного значения с разделяемым состоянием. Контейнеры создаются через collect, где модель объединения предусмотрена специально.

Filter до группировки и после неё решает разные задачи

В примере задача короче 15 минут исключается до суммирования. Поэтому web получает 20. Если сначала сложить всё по отделам, получится web=30; последующая фильтрация групп по порогу означает уже «отделы с суммой не меньше порога». Эти постановки похожи в коде и различны для читателя отчёта.

Аналогично distinct удаляет дубликаты по равенству элементов. Он не является способом «убрать лишние строки»: два одинаковых заказа могут быть двумя реальными покупками. Прежде чем применять операцию, назовите бизнес-идентичность события.

Ошибки при построении Map

У Collectors.toMap(keyMapper, valueMapper) повторяющиеся ключи приводят к отказу; для допустимых повторов задайте политику объединения. «Оставить первый» подходит не каждой задаче: для суммы нужны сложение и проверка диапазона, для конфликтующих версий — сравнение версии или явная ошибка. groupingBy лучше выражает задачу сохранить все элементы одного ключа. Построение составных collectors.

Проверьте null до классификации и не рассчитывайте на одинаковую политику разных collectors. Также summingInt не обеспечивает защиту от переполнения. Время небольшого учебного примера помещается в int, но сумма денежных значений или длинного журнала требует выбранного числового контракта и отдельной проверки границ.

Самостоятельная задача с двумя ответами

Добавьте new Task("ops", 5). Предскажите, в каких результатах появится ops: в первой карте он отсутствует, в счётчике должен появиться, а общая сумма станет 90. Затем составьте другой отчёт: сначала суммируйте все задачи, после этого оставьте отделы с итогом не меньше 30. Для исходных четырёх записей должны остаться core=55 и web=30.

Проверьте пустой список: суммы равны нулю, карты пусты, anyMatch возвращает false. Для минимума или максимума не придумывайте нулевое значение, если оно меняет смысл задачи: удобнее явно обработать Optional результата.

Частые вопросы

groupingBy всегда создаёт список значений?

Только простой вариант без downstream collector собирает группы элементов. Передайте counting, summingInt, mapping или другой подходящий collector, когда нужны счётчики, агрегаты либо преобразованные значения.

Можно ли механически заменить sum на reduce?

Для выбранных целых значений и правильного нейтрального элемента можно получить тот же ответ. Но sum лучше выражает обычную сумму, а reduce требует внимательно проверить тип результата, нейтральность и свойства операции объединения.

Источники