Подсчёт заказов по статусу через Collectors.groupingBy
Дан список заказов. Верните, сколько заказов приходится на каждый статус. Используйте Stream API и Collectors.groupingBy, чтобы всё уместилось в один терминальный collect.
Ограничения:
- один проход по stream — не группировать, а затем считать вторым циклом
- пустой вход возвращает пустой map, а не
null
enum Status { NEW, PAID, SHIPPED }
record Order(Status status, int amount) {}
Map<Status, Long> countByStatus(List<Order> orders) {
// ваш код здесь
}
Допишите реализацию.
groupingBy(classifier) применяет классификатор к каждому элементу и собирает элементы в Map<K, List<T>> с ключом по его результату. Двухаргументный groupingBy(classifier, downstream) пропускает каждую корзину через второй коллектор вместо списка элементов, поэтому groupingBy(Order::status, counting()) даёт Map<Status, Long> со счётчиками за один проход: orders.stream().collect(groupingBy(Order::status, counting())).
- ✗Ждать обычный
Map<K, List<T>>и затем считать циклом вместо передачиcounting() - ✗Думать, что
groupingByсортирует ключи или требуетComparable-ключ, какTreeMap - ✗Считать, что downstream-коллектор меняет ключи, а не сворачивает каждую корзину значений
- →Чем
groupingBy(classifier, mapping(...))отличается отgroupingBy(classifier, counting())? - →Когда стоит передать поставщик map третьим аргументом в
groupingBy?
Решение
import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.groupingBy;
Map<Status, Long> countByStatus(List<Order> orders) {
return orders.stream()
.collect(groupingBy(Order::status, counting()));
}
Как это работает. groupingBy берёт классификатор — функцию ключа Order::status — и раскладывает элементы по корзинам: результат классификатора становится ключом map. Одноаргументная форма собрала бы Map<Status, List<Order>>.
Второй аргумент — downstream-коллектор, который применяется к каждой корзине вместо накопления списка. counting() сворачивает корзину в её размер, поэтому итог — Map<Status, Long>. Всё считается за один терминальный collect; пустой вход честно даёт пустой map. Если бы нужны были не списки, а, скажем, суммы, downstream заменили бы на summingInt(Order::amount).