HashMap в Java— что за карта такая?

Статья из группы Java Developer
Привет! Сегодня мы поговорим о еще одной структуре данных — Map. Ее официальное русское название — “ассоциативный массив”, но его используют нечасто. Более распространены варианты “словарь”, “карта”, или (чаще всего) — сленговый англицизм “мапа” :) Внутри Map данные хранятся в формате “ключ”-”значение”, то есть по парам. И в качестве ключей, и в качестве значений могут выступать любые объекты — числа, строки или объекты других классов.

Отличие Map от других структур данных

Ранее мы разбирали структуры данных, где элементы хранятся сами по себе. В массиве, или списке ArrayList/LinkedList мы храним какое-то количество элементов. Но что, если наша задача немного изменится? Например, представь себе, что перед нами стоит задача: создать список из 100 человек, где будет храниться ФИО человека и номер его паспорта. В принципе, это не так сложно. Например, можно уместить и то, и другое в строку, и создать список вот таких строк: “Анна Ивановна Решетникова, 4211 717171”. Но у такого решения сразу два недостатка. Во-первых, нам может понадобиться функция поиска по паспорту. А при таком формате хранения информации это будет проблематично. А во-вторых, ничто не помешает нам создать двух разных людей с одинаковыми номерами паспорта. И это самый серьезный недостаток нашего решения. Такие ситуации должны быть полностью исключены, не бывает двух людей с одинаковым номером паспорта. Тут на помощь нам приходит Map и ее заявленные особенности (хранение данных по паре в формате “ключ”-”значение”). Давай рассмотрим самую распространенную реализацию Map — Java класс HashMap.HashMap — что за карта такая? - 1

Создание HashMap в Java и работа с классом

Создается данная реализация очень просто:

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

} 
Здесь мы создали словарь, в котором элементы будут храниться в формате “число-строка”. Число будет выступать ключом, а строка — значением. Также мы указали какого типа у нас будут ключи (Integer), а какого — значения (String). Почему именно так? Во-первых, ключ в HashMap всегда является уникальным. Для нас это отлично подойдет, поскольку мы сможем использовать номер паспорта в качестве ключа и избежать повторов. А строка с ФИО будет выступать значением (ФИО у разных людей легко могут повторяться, в этом ничего страшного для нас нет).

Добавление новой пары в HashMap

Данная задача выглядит так:

public class Main { 

   public static void main(String[] args) { 
       HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

 
       passportsAndNames.put(212133, "Лидия Аркадьевна Бубликова"); 
       passportsAndNames.put(162348, "Иван Михайлович Серебряков"); 
       passportsAndNames.put(8082771, "Дональд Джон Трамп"); 
       System.out.println(passportsAndNames); 

   } 

}
Для этого используется метод put(). Кроме того, HashMap имеет переопределенный метод toString(), поэтому ее можно выводить на консоль. Вывод будет выглядеть так: {212133=Лидия Аркадьевна Бубликова, 8082771=Дональд Джон Трамп, 162348=Иван Михайлович Серебряков}

Особенности ключей HashMap

Теперь давай проверим, действительно ли ключи являются уникальными? Попробуем добавить новый элемент с уже имеющимся в мапе ключом:

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лидия Аркадьевна Бубликова"); 
   passportsAndNames.put(162348, "Иван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 
   passportsAndNames.put(162348, "Виктор Михайлович Стычкин");//повторный ключ 

   System.out.println(passportsAndNames); 

}
Вывод: {212133=Лидия Аркадьевна Бубликова, 8082771=Дональд Джон Трамп, 162348=Виктор Михайлович Стычкин} Предыдущий элемент с ключом 162348, как видишь, был перезаписан. “Ключ” назвали ключом не просто так. Доступ к значениям в HashMap осуществляется по ключу (но никак не наоборот — ключ нельзя получить по значению, ведь значения могут быть повторяющимися). Это хорошо видно на примерах получения элемента, а также удаления элемента из HashMap:

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лидия Аркадьевна Бубликова"); 
   passportsAndNames.put(162348, "Иван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 

   String lidiaName = passportsAndNames.get(212133); 
   System.out.println(lidiaName); 


   passportsAndNames.remove(162348); 
   System.out.println(passportsAndNames); 

}
Для того, чтобы получить значение, или удалить пару из словаря, мы должны передать в методы get() и remove() именно уникальный ключ, соответствующий этому значению. Номерных индексов, как в массивах или списках, в HashMap нет — доступ к значению осуществляется по ключу. Вывод в консоль: Лидия Аркадьевна Бубликова {212133=Лидия Аркадьевна Бубликова, 8082771=Дональд Джон Трамп}

Проверка наличия ключа и значения

В классах ArrayList и LinkedList мы могли проверить, содержится ли в списке какой-то конкретный элемент. HashMap тоже позволяет это делать, причем для обеих частей пары: у нее есть методы containsKey()(проверяет наличие какого-то ключа) и containsValue() (проверяет наличие значения).

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лидия Аркадьевна Бубликова"); 
   passportsAndNames.put(162348, "Иван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 


   System.out.println(passportsAndNames.containsKey(11111)); 
   System.out.println(passportsAndNames.containsValue("Дональд Джон Трамп")); 

}
Вывод: false true

Получение списка всех ключей и значений

Еще одна удобная особенность HashMap — можно по-отдельности получить список всех ключей и всех значений. Для этого используются методы keySet() и values():

public class Main { 
 
   public static void main(String[] args) { 

       HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

       passportsAndNames.put(212133, "Лидия Аркадьевна Бубликова"); 
       passportsAndNames.put(162348, "Иван Михайлович Серебряков"); 
       passportsAndNames.put(8082771, "Дональд Джон Трамп"); 

       Set<Integer> keys = passportsAndNames.keySet(); 
       System.out.println("Ключи: " + keys); 

       ArrayList<String> values = new ArrayList<>(passportsAndNames.values()); 
       System.out.println("Значения: " + values); 

   } 

}
Ключи извлекаются в коллекцию Set. Ее особенность в том, что в ней не может быть повторяющихся элементов. Сейчас главное запомни, что список всех ключей можно вынести из HashMap в отдельную коллекцию. Значения мы в примере сохранили в обычный ArrayList. Вывод в консоль: Ключи: [212133, 8082771, 162348] Значения: [Лидия Аркадьевна Бубликова, Дональд Джон Трамп, Иван Михайлович Серебряков] Методы size() и clear() делают ровно то же самое, что и в предыдущих структурах, которые мы проходили: первый — возвращает число элементов в словаре на текущий момент, второй — удаляет все элементы.

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лидия Аркадьевна Бубликова"); 
   passportsAndNames.put(162348, "Иван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 

   System.out.println(passportsAndNames.size()); 
   passportsAndNames.clear(); 
   System.out.println(passportsAndNames); 

}
Вывод: 3 {} Для проверки того, есть ли в нашей HashMap хотя бы один элемент, можно использовать метод isEmpty():

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лидия Аркадьевна Бубликова"); 
   passportsAndNames.put(162348, "Иван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 
 
   if (!passportsAndNames.isEmpty()) { 

       System.out.println(passportsAndNames); 

   } 

}
Вывод: {212133=Лидия Аркадьевна Бубликова, 8082771=Дональд Джон Трамп, 162348=Иван Михайлович Серебряков} Теперь вывод на консоль у нас будет осуществляться только после предварительной проверки:)

Объединение двух мап в одну

Еще один интересный момент — две мапы можно объединить в одну. Для этого существует метод putAll(). Мы вызываем его у первой HashMap, передаем вторую в качестве аргумента, и элементы из второй будут добавлены в первую:

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 
   HashMap<Integer, String> passportsAndNames2 = new HashMap<>(); 
 
   passportsAndNames.put(212133, "Лидия Аркадьевна Бубликова"); 
   passportsAndNames.put(162348, "Иван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 
 
   passportsAndNames2.put(917352, "Алексей Андреевич Ермаков"); 
   passportsAndNames2.put(925648, "Максим Олегович Архаров"); 


   passportsAndNames.putAll(passportsAndNames2); 
   System.out.println(passportsAndNames); 

}
Вывод: {917352=Алексей Андреевич Ермаков, 212133=Лидия Аркадьевна Бубликова, 8082771=Дональд Джон Трамп, 925648=Максим Олегович Архаров, 162348=Иван Михайлович Серебряков} Все элементы passportsAndNames2 были скопированы в passportsAndNames. Теперь рассмотрим пример посложнее. А именно — перебор HashMap в цикле.

for (Map.Entry entry: passportsAndNames.entrySet()) { 

   System.out.println(entry); 

}
Интерфейс Map.Entry обозначает как раз пару “ключ-значение” внутри словаря. Метод entrySet() возвращает список всех пар в нашей HashMap (поскольку наша мапа состоит как раз из таких пар-Entry, то мы перебираем именно пары, а не отдельно ключи или значения). Вывод: 212133=Лидия Аркадьевна Бубликова 8082771=Дональд Джон Трамп 162348=Иван Михайлович Серебряков Сохрани себе на будущее вот эту статью: https://habr.com/ru/post/128017/ Сейчас ее пока читать рановато, но в будущем, когда ты набьешь руку в использовании HashMap, она поможет тебе разобраться как эта структура данных устроена изнутри. Кроме того, не забудь изучить официальную документацию Oracle по HashMap.
Комментарии (123)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Lonlost Уровень 26, Ul'yanovsk, Russian Federation
9 мая 2022
Почему в этом примере:

public static void main(String[] args) {

   HashMap<Integer, String> passportsAndNames = new HashMap<>();
   HashMap<Integer, String> passportsAndNames2 = new HashMap<>();

   passportsAndNames.put(212133, "Лидия Аркадьевна Бубликова");
   passportsAndNames.put(162348, "Иван Михайлович Серебряков");
   passportsAndNames.put(8082771, "Дональд Джон Трамп");

   passportsAndNames2.put(917352, "Алексей Андреевич Ермаков");
   passportsAndNames2.put(925648, "Максим Олегович Архаров");


   passportsAndNames.putAll(passportsAndNames2);
   System.out.println(passportsAndNames);

}
данные из второй мапы добавились в разнобой? {917352=Алексей Андреевич Ермаков, 212133=Лидия Аркадьевна Бубликова, 8082771=Дональд Джон Трамп, 925648=Максим Олегович Архаров, 162348=Иван Михайлович Серебряков}
John F Уровень 23
29 марта 2022
Кто-нибудь понял зачем перед "passportsAndNames" стоит "!" ?
Сергей Иванов Уровень 39
21 марта 2022
Спасибо автору за статью. Очень доступно и понятно.
Anonymous #2875711 Уровень 1, North Bergen, United States
3 марта 2022
hash.forEach((k,v) -> System.out.println(k + " " + v)); так переберать мапу проще наверное
Dr.Pro-x Уровень 6, Ногинск, Russian Federation
15 февраля 2022
Долгое время обходил стороной HashMap не понимая как он работает. Наизобритал кучу велосипедов, хоть музей открывай. А оно вон как просто оказывается. С каждым абзацем прикидовал изменения в рабочих программах. Вот есть же люди способные толково объяснять. Автору большое СПАСИБО
Кирилл Востругин Уровень 26, Кемерово , Russian Federation
9 февраля 2022
Кто-нибудь читает официальную документацию Oracle?
marych Уровень 29, Russian Federation
29 января 2022
Спасибо!
YesOn Уровень 8, Томск, Россия
19 января 2022
Спасибо за интересную и упорядоченную статью! Хорошие примеры, всё понятно!🙂👍
Rimma Уровень 1, Canada
4 января 2022
Скажите, пожалуйста, почему ItelliJ подчеркивает типы красным? Спасибо
Rimma Уровень 1, Canada
4 января 2022
спасибо! все легко и доступно!!!