Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Группировка

Группировка раскладывает элементы по корзинам: все заказы одного покупателя — в одну, все слова одной длины — в другую. В Stream API для этого есть коллектор Collectors.groupingBy. Результат — словарь, где ключ — признак группы, а значение — её элементы.

groupingBy

Сгруппируем слова по длине. Функция, переданная в groupingBy, вычисляет ключ группы для каждого элемента:

void main() {
    List<String> words = List.of("кот", "лес", "мост", "сад", "дорога", "луна");
 
    Map<Integer, List<String>> byLength = words.stream()
            .collect(Collectors.groupingBy(w -> w.length()));
 
    IO.println(byLength);
}

Результат:

{3=[кот, лес, сад], 4=[мост, луна], 6=[дорога]}

Каждое значение словаря — список слов этой длины, порядок внутри списка такой же, как в исходных данных. Тип результата — Map<Integer, List<String>>.

Что делать с группой

Вторым аргументом groupingBy принимает ещё один коллектор — он решает, что сделать с элементами каждой группы. Его называют нижестоящим (downstream). Например, вместо списков можно сразу посчитать количество или сумму:

record Order(String customer, int amount) {}
 
void main() {
    List<Order> orders = List.of(
            new Order("Анна", 20), new Order("Олег", 35),
            new Order("Анна", 15), new Order("Ира", 50));
 
    Map<String, Long> count = orders.stream()
            .collect(Collectors.groupingBy(Order::customer, TreeMap::new, Collectors.counting()));
    Map<String, Integer> total = orders.stream()
            .collect(Collectors.groupingBy(Order::customer, TreeMap::new, Collectors.summingInt(Order::amount)));
 
    IO.println(count);
    IO.println(total);
}

Результат:

{Анна=2, Ира=1, Олег=1}
{Анна=35, Ира=50, Олег=35}

Средний аргумент TreeMap::new задаёт тип словаря. TreeMap хранит ключи отсортированными, поэтому имена идут по алфавиту.

mapping

Коллектор Collectors.mapping преобразует элементы группы перед сборкой. Так можно собрать не заказы целиком, а только суммы:

record Order(String customer, int amount) {}
 
void main() {
    List<Order> orders = List.of(
            new Order("Анна", 20), new Order("Олег", 35), new Order("Анна", 15));
 
    Map<String, List<Integer>> amounts = orders.stream()
            .collect(Collectors.groupingBy(
                    Order::customer,
                    TreeMap::new,
                    Collectors.mapping(Order::amount, Collectors.toList())));
 
    IO.println(amounts);
}

Результат:

{Анна=[20, 15], Олег=[35]}

partitioningBy

partitioningBy — частный случай группировки: всего две группы, true и false. Ключом служит результат условия:

void main() {
    List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);
 
    Map<Boolean, List<Integer>> parts = numbers.stream()
            .collect(Collectors.partitioningBy(n -> n % 2 == 0));
 
    IO.println("Чётные: " + parts.get(true));
    IO.println("Нечётные: " + parts.get(false));
}

Результат:

Чётные: [2, 4, 6]
Нечётные: [1, 3, 5, 7]

В отличие от groupingBy, обе группы есть в словаре всегда — даже если одна из них пустая.

Обход результата

Результат группировки — обычный словарь, его можно обойти через entrySet() или forEach:

void main() {
    List<String> fruits = List.of("яблоко", "груша", "ананас", "голубика", "абрикос");
 
    fruits.stream()
            .collect(Collectors.groupingBy(f -> f.charAt(0), TreeMap::new, Collectors.toList()))
            .forEach((letter, list) -> IO.println(letter + ": " + list));
}

Результат:

а: [ананас, абрикос]
г: [груша, голубика]
я: [яблоко]

Частая ошибка

По умолчанию groupingBy складывает группы в HashMap, а он не обещает никакого порядка ключей. Код может годами выводить группы в «правильном» порядке, а после смены данных или версии Java — перемешать их:

void main() {
    List<String> names = List.of("Олег", "Анна", "Ира", "Борис", "Вера", "Алла");
 
    IO.println(names.stream().collect(Collectors.groupingBy(n -> n.substring(0, 2))));
}

Результат (порядок ключей не гарантирован):

{Ве=[Вера], Ир=[Ира], Ал=[Алла], Бо=[Борис], Ан=[Анна], Ол=[Олег]}

Если порядок важен, передавайте фабрику словаря явно: TreeMap::new для сортировки по ключу или LinkedHashMap::new для порядка появления.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.