Коллекторы 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