JavaRush/Java блог/Java Developer/HashMap в Java— что за карта такая?
Автор
Milan Vucic
Репетитор по программированию в Codementor.io

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.
Комментарии (183)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
{Java_Shark}
Уровень 19
26 февраля, 07:15
Отличная статья!!! Автору респект и +100 к карме)))
JavaRusher853
Уровень 25
16 февраля, 20:36
Очень интересная статья, реально не мог оторваться. Много новых (и повторение старых в том числе) и полезных методов, однозначно статья в копилку.
Denis Konshin
Уровень 11
9 февраля, 18:57
интересненько
Алексей
Уровень 46
Expert
30 декабря 2023, 12:02
Хорошая статья 👍
Вячеслав колесико в мышке Expert
10 декабря 2023, 22:13
🧐
Максим Li Java Developer
5 декабря 2023, 16:38
Отлично!
Alex
Уровень 18
21 ноября 2023, 15:24
Отличная статья, автору спасибо
Anatoly Enterprise Java Developer
16 августа 2023, 12:55
okr
Alexander Rozenberg
Уровень 32
18 июля 2023, 20:43
fine
Skotique
Уровень 35
27 июня 2023, 02:18
довольно базовое описание контейнера хэшмэп. на деле это массив, из ячеек которого могут вырастать однонаправленные связные списки. сия конструкция может напоминать расческу с выломанными зубьями разной длины. эта лекция ни о чем, изучайте плотнее!
Anonymous cat
Уровень 30
Expert
13 августа 2023, 17:54
Поделитесь, пожалуйста, ссылками или источниками из которых лучше изучить данный вопрос
Пётр
Уровень 41
Expert
4 ноября 2023, 15:18
Ну уж куда глубже - открываем класс HashMap и смотрим его структуру. И что же мы видим - * This map usually acts as a binned (bucketed) hash table, but * when bins get too large, they are transformed into bins of * TreeNodes, each structured similarly to those in * java.util.TreeMap. Каким боком тут могут вырастать однонаправленные связанные списки? Или вы читаете доку по джаве 1.7 во времена выхода 20й?