Як зібрати 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]]