Как собрать Stream в Map?
Для сборки в Map используется Collectors.toMap():
🟢 Junior Level
Для сборки в Map используется Collectors.toMap():
toMap() = один элемент на ключ (если ключи дублируются → IllegalStateException). groupingBy() = список элементов на ключ (дубликаты группируются).
Map<Integer, String> idToName = users.stream()
.collect(Collectors.toMap(
User::getId, // ключ
User::getName // значение
));
Простой пример:
List<User> users = ...;
Map<String, User> nameToUser = users.stream()
.collect(Collectors.toMap(User::getName, u -> u));
Если у двух элементов одинаковый ключ — получите ошибку IllegalStateException.
🟡 Middle Level
Сигнатуры toMap
Базовая (2 аргумента):
.toMap(keyMapper, valueMapper)
// При коллизии → IllegalStateException
С merge function (3 аргумента):
.toMap(keyMapper, valueMapper, (old, replacement) -> old)
// При коллизии — сохраняет старое значение
// mergeFunction вызывается ТОЛЬКО при дубликате ключа. // Она решает, какое значение оставить: старое, новое, или объединить.
С фабрикой Map (4 аргумента):
.toMap(keyMapper, valueMapper, mergeFn, LinkedHashMap::new)
// Сохраняет порядок вставки
GroupingBy
Для группировки элементов (один ключ — много значений):
// Группировка по городу
Map<City, List<Person>> byCity = persons.stream()
.collect(groupingBy(Person::getCity));
// Группировка + подсчёт
Map<City, Long> countByCity = persons.stream()
.collect(groupingBy(Person::getCity, counting()));
Когда НЕ использовать toMap
- Ключи могут повторяться — используйте
groupingBy(), иначе IllegalStateException - Нужно только количество —
groupingBy(counting())вместо toMap + size() - Один ключ → много значений —
groupingBy()илиtoMultimap()
🔴 Senior Level
EnumMap Optimization
Если ключи — Enum, стандартная HashMap неэффективна:
.collect(groupingBy(User::getRole, () -> new EnumMap<>(Role.class), toList()))
EnumMap использует массив → O(1) доступ и минимальная память.
Предотвращение рехеширования
Если знаете размер данных:
.collect(toMap(k, v, merge, () -> new HashMap<>(expectedSize)))
Null Values
Collectors.toMap не позволяет null в значениях, даже если целевая HashMap поддерживает. Это ограничение реализации.
Решение: Используйте forEach или собирайте в Optional.
Identity Function
Используйте Function.identity() вместо x -> x — в некоторых JDK работает эффективнее за счет отсутствия создания лямбда-объекта.
Parallel Streams
groupingByConcurrent для параллельных стримов — использует ConcurrentHashMap, потоки пишут в одну мапу без combiner. Группировка без groupingByConcurrent требует Map.putAll() — может быть медленнее последовательного.
Диагностика
При IllegalStateException в toMap логируйте ключи-дубликаты. Стандартное исключение JDK не всегда информативно.
🎯 Шпаргалка для интервью
Обязательно знать:
Collectors.toMap(keyMapper, valueMapper)— базовая сборка, при коллизии бросает IllegalStateException- 3-й аргумент toMap — merge function:
(old, replacement) -> old(оставить первый) - 4-й аргумент — фабрика Map:
LinkedHashMap::newсохраняет порядок вставки groupingBy()— когда одному ключу соответствует много значений (Map<K, List>) groupingBy + counting()— для подсчёта элементов по группамtoMapне допускает null в значениях — ограничение реализацииgroupingByConcurrentдля параллельных стримов — пишет в ConcurrentHashMap без combiner- Для Enum-ключей используйте
EnumMap— O(1) доступ, минимальная память
Частые уточняющие вопросы:
- Что будет при дубликате ключа в toMap? — IllegalStateException. Решение: добавить merge function третьим аргументом.
- Чем toMap отличается от groupingBy? — toMap: один элемент на ключ; groupingBy: список элементов на ключ (Map<K, List
>). - Как сохранить порядок вставки? — Четвёртый аргумент:
.toMap(key, value, merge, LinkedHashMap::new). - Почему toMap не принимает null-значения? — Ограничение внутренней реализации JDK через Map.merge, которая бросает NPE на null.
Красные флаги (НЕ говорить):
- «toMap автоматически разрешает коллизии» — неверно, без merge function бросит IllegalStateException
- «groupingBy возвращает Map<K, V>» — неверно, возвращает Map<K, List
> - «toMap поддерживает null значения» — неверно, бросит NPE
- «parallelStream с toMap всегда быстрее» — неверно, без groupingByConcurrent может быть медленнее из-за combiner
Связанные темы:
- [[28. Что делать при коллизиях ключей при сборке в Map]]
- [[23. Что делают операции distinct(), sorted(), limit(), skip()]]
- [[29. Как работать с Optional в Stream]]
- [[21. Что такое lazy evaluation в Stream]]