TGViewer
Java: fill the gaps Java: fill the gaps @java_fillthegaps · 12.3K subscribers
Post #201 5.03K
Коллекторы Stream API, часть 2: сложные коллекторы

В этой части разберёмся в коллекторах toMap, groupingBy и partitioningBy. Уверена, года через 3 их будут спрашивать на занудных собеседованиях.

Будем тренироваться на классе Student из вопроса перед постом. У него есть id, имя, город и список оценок.

🔸toMap, toConcurrentMap
Цель понятна: уложить элементы стрима в map. Указываем функцию для ключа и для значения:
collect(toMap(Student::id, Student::name))

Если ключи повторяются, то вылетит IllegalStateException. Чтобы этого избежать, укажите третьим параметром, как объединять значения:

collect(toMap(Student::city, 
Student::name,
(a,b) -> a+" "+b))

["Москва":"Антон Аня Эдуард"]
Похоже на группировку по городу, но это не она. В результате получается одно значение, а не группа.

🔸groupingBy
Настоящую группировку делает коллектор groupingBy:

Map<String, List<Student>>=…
collect(groupingBy (Student::city))

По умолчанию сгруппированные элементы объединяются в список. Чтобы получить что-нибудь другое, передайте в метод другой коллектор:

groupingBy(Student::city, toSet())

Здесь нам наконец пригодятся коллекторы из предыдущего поста, которые подставим во второй аргумент.
▫️Города и количество студентов:
groupingBy(Student::city, counting())

▫️Города и имена студентов:
groupingBy(Student::city, 
mapping(Student::name, toSet()))

Передаём в groupingBy функцию для ключей и коллектор mapping для значений. Он же принимает другой коллектор toSet, чтобы было понятно, куда складывать результат.

▫️ID студента и средняя оценка за экзамены:
...
Для этой задачи группировка не подходит. У одного студента только одна средняя оценка, количество итоговых элементов = количеству исходных. Группировать нечего, поэтому используем toMap:

toMap(Student::id,
s -> s.marks().stream().расчёт())

Вместо расчёт() - километр кода. Подсчёт среднего недоступен в интерфейсе Stream, а удобных методов перевода List<Integer> в IntStream нет.

🔸partitioningBy
Метод делит элементы на две группы по заданному условию. Результат - map с двумя ключами - true и false.
▫️Делим студентов на москвичей и жителей других городов:
Map<Boolean,Set<Student>>…
partitioningBy(s-> s.city().equals("Moscow"), toSet())

Вместо toSet можно подставить другой коллектор. Посчитаем количество студентов:

partitioningBy(s-> s.city().equals("Moscow"), counting())

Не очень понятно, зачем нужен partitioningBy. groupingBy даёт такой же результат, но на 2 символа короче:

groupingBy(s-> s.city().equals("Moscow"), counting())

❓Ответ на вопрос перед постом

Вы поручили джуниору посчитать среди студентов тех, кто не сдал ни одного экзамена. Вариант 3 предлагает применить к набору студентов метод Integer::longValue, что невозможно. Остальные варианты переводят каждого студента в число 1 и суммируют все значения. Ошибочный ответ - 3.

#core
  • 👍 3
More from @java_fillthegaps
  1. Apr 30, 2026Get Your Hands Dirty on Clean Architecture: отзыв на книгу Когда я подняла тему чистой арх…
  2. Apr 27, 2026​Clean Architecture: отзыв на книгу Наконец-то дочитала книгу Clean Architecture Роберта М…
  3. Mar 4, 2026Как переиспользовать контекст в интеграционных тестах Сегодня расскажу базовый минимум для…
  4. Mar 4, 2026Post #660
  5. Mar 4, 2026Тестовый контекст поднимается 2 минуты. У нас 4 класса с интеграционными тестами, их конфи…
  6. Feb 25, 2026Чистая архитектура. Главы 3-5 Продолжаем спидран по Clean Architecture Роберта Мартина. ⭐️…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →