Как перебрать HashMap в Java
Перебор записей Java HashMap с помощью entrySet, keySet, values, forEach и потоков.
HashMap хранит пары ключ-значение, и рано или поздно вам понадобится пройтись по ним — чтобы вывести отчёт, подсчитать сумму значений или отфильтровать записи. Java предлагает несколько идиоматичных способов сделать это, каждый из которых подходит для немного разных задач: нужны ли вам ключи, значения или и то и другое? В этой главе рассматриваются наиболее распространённые подходы — entrySet(), keySet(), values(), forEach и удаляющий Iterator — и объясняется, когда к ним следует прибегать.
Эти приёмы применимы к любой реализации Map, включая LinkedHashMap и TreeMap, поскольку все они разделяют единый API итерации.
Перебор записей с помощью entrySet()
Когда вам нужны и ключ, и значение, entrySet() является наиболее эффективным выбором. Он возвращает представление объектов Map.Entry, и за один проход вы получаете каждую пару без дополнительного поиска:
Map<String, Integer> stock = new HashMap<>();
for (Map.Entry<String, Integer> e : stock.entrySet()) {
System.out.println(e.getKey() + " -> " + e.getValue());
}Это рекомендуемый подход по умолчанию. Использование keySet() с последующим вызовом map.get(key) внутри цикла выполняет избыточный хэш-поиск для каждого элемента; entrySet() полностью избавляет от этого.
Итерация только ключей или только значений
Если вас интересует лишь одна сторона каждой пары, запросите только это представление. keySet() возвращает ключи, а values() возвращает значения:
for (String key : stock.keySet()) {
System.out.println("key: " + key);
}
for (int qty : stock.values()) {
System.out.println("qty: " + qty);
}Оба представления опираются на карту, поэтому они отражают её текущее содержимое без копирования. Используйте keySet(), когда значения вам действительно не нужны, и values(), когда ключи не важны.
Метод forEach
Начиная с Java 8, у Map есть метод forEach, принимающий BiConsumer и передающий ключ и значение в качестве параметров лямбды. Он лаконичен и хорошо читается для простых побочных эффектов:
stock.forEach((key, value) -> System.out.println(key + "=" + value));Внутри лямбды нет break или continue, поэтому для раннего выхода или сложного управления потоком классический цикл for по-прежнему нагляднее.
Безопасное удаление с помощью Iterator
Структурное изменение карты во время работы цикла for-each вызывает ConcurrentModificationException. Чтобы удалять записи в процессе обхода, используйте явный Iterator и вызывайте его метод remove():
Iterator<Map.Entry<String, Integer>> it = stock.entrySet().iterator();
while (it.hasNext()) {
if (it.next().getValue() < 10) {
it.remove();
}
}Современная альтернатива — stock.entrySet().removeIf(e -> e.getValue() < 10), которая выражает тот же фильтр в одну строку.
| Подход | Даёт вам | Лучше всего для |
|---|---|---|
entrySet() | ключ + значение | вариант по умолчанию; чтение обоих |
keySet() | только ключи | работа с ключами |
values() | только значения | суммы, сканирование значений |
forEach | ключ + значение (лямбда) | лаконичные побочные эффекты |
Iterator | ключ + значение | удаление в процессе обхода |
Что можно извлечь из запуска:
- Цикл
entrySet()считывает каждый ключ и значение за один проход и накапливаетTotal stock: 39, суммируя 12 + 7 + 20. keySet()выводит только ключи (apple,banana,cherry), тогда какvalues()— только числа, демонстрируя, что каждое представление открывает одну сторону пары.- Лямбда
forEachвыдаёт те же строки key=value, что и ручной цикл, подтверждая, что это лаконичный эквивалент для простой итерации. - Был использован
LinkedHashMap, чтобы вывод сохранял порядок вставки — обычныйHashMapне даёт гарантий порядка, поэтому его строки могут появляться в любой последовательности. - Вызов
Iterator.remove()удаляетbanana(значение 7, меньше 10) и оставляет{apple=12, cherry=20}, демонстрируя безопасное удаление внутри цикла безConcurrentModificationException.