Интерфейс Map в Java: методы, Map.Entry и примеры - Вопросы
Всего: 5 вопросов
1. Чем отличаются методы keySet(), values() и entrySet() интерфейса Map?
Чем отличаются методы keySet(), values() и entrySet() интерфейса Map?
keySet() возвращает Set<K> ключей, values() — Collection<V> значений (не Set, потому что значения могут повторяться), а entrySet() — Set<Map.Entry<K, V>> пар, у каждой из которых есть getKey(), getValue() и setValue().
Все три метода возвращают представления (views) самой карты, а не копии: keySet().remove(key) удаляет пару из карты, а entry.setValue() меняет значение прямо в ней. Добавлять через представления нельзя — keySet().add() бросает UnsupportedOperationException, ведь для нового ключа нет значения.
Когда нужны и ключ, и значение, обходят entrySet() или вызывают forEach((k, v) -> ...): обход keySet() с get(key) внутри цикла ищет каждый ключ повторно.
2. Как в Map отличить отсутствующий ключ от ключа, которому сопоставлено значение null?
Как в Map отличить отсутствующий ключ от ключа, которому сопоставлено значение null?
Метод get(key) возвращает null в обоих случаях, поэтому по его результату их не различить. Единственный надёжный способ — containsKey(key): он возвращает true, если ключ есть, даже когда его значение равно null.
getOrDefault(key, defaultValue) тоже не помогает: значение по умолчанию возвращается, только если ключа нет совсем, а для ключа со значением null метод вернёт null. Похожая ловушка у putIfAbsent() — ключ со значением null он считает отсутствующим и перезаписывает.
Если null в значениях возможен, используют Objects.requireNonNullElse(map.get(key), defaultValue) (Java 9) или вовсе не хранят null в карте.
3. Как перебрать Map и безопасно удалить из неё элементы во время обхода?
Как перебрать Map и безопасно удалить из неё элементы во время обхода?
Map перебирают циклом for-each по entrySet(), когда нужны и ключ, и значение, по keySet() или values(), когда нужно что-то одно, или методом forEach((k, v) -> ...) из Java 8.
Вызывать map.remove(key) внутри for-each нельзя: следующий шаг итератора, как правило, бросит ConcurrentModificationException. Безопасные варианты — map.entrySet().removeIf(e -> e.getValue() < 1250), keySet().removeIf(...), values().removeIf(...) или явный итератор с вызовом iterator.remove(). Они удаляют пары через представление, и изменения сразу отражаются в самой карте.
4. Как работает метод merge() в Map и как с его помощью посчитать частоту слов?
Как работает метод merge() в Map и как с его помощью посчитать частоту слов?
merge(key, value, remappingFunction) появился в Java 8. Если ключа нет, метод кладёт value; если ключ есть, вызывает функцию со старым и новым значением и сохраняет результат. Если функция вернула null, пара удаляется, а value == null приводит к NullPointerException.
Частота слов считается одной строкой: counts.merge(w, 1, Integer::sum) — для нового слова кладётся 1, для существующего к счётчику прибавляется 1. Вариант counts.put(w, counts.get(w) + 1) падает с NullPointerException на первом же слове: get() возвращает null, а распаковать его в int нельзя.
Для группировки значений в списки используют родственный метод computeIfAbsent(key, k -> new ArrayList<>()).add(value).
5. Какие ограничения у карт, созданных через Map.of(), и чем они отличаются от new HashMap<>()?
Какие ограничения у карт, созданных через Map.of(), и чем они отличаются от new HashMap<>()?
Map.of(k1, v1, k2, v2, ...) (Java 9) создаёт неизменяемую карту из не более чем 10 пар; для большего числа пар есть Map.ofEntries(Map.entry(k, v), ...), а Map.copyOf(map) (Java 10) делает неизменяемую копию.
У таких карт три ограничения: любые изменения (put(), remove(), clear()) бросают UnsupportedOperationException; повторяющийся ключ — IllegalArgumentException: duplicate key (повторяющиеся значения допустимы); null в ключах и значениях запрещён, даже get(null) бросает NullPointerException. Порядок обхода не определён.
new HashMap<>() изменяема и допускает один null-ключ и null-значения. Изменяемую карту с начальными данными получают обёрткой new HashMap<>(Map.of(...)).