1. Множество пар: ключ-значение.

Множество пар: ключ-значение

В Java есть еще одна интересная коллекция (в широком смысле) — это коллекция Map. Точного перевода ее названия на русский нет: чаще всего ее называют «карта», «словарь» или просто «мапа».

Эта коллекция похожа на коллекцию Set, только хранит не множество элементов, а множество «пар элементов». Каждая пара элементов Map состоит из двух: «ключ» и «значение».

Допустим, вы хотите хранить в программе имена сотрудников компании, их зарплаты или имена ваших коллег и их возраст. Тогда вам бы понадобилась таблица типа такой:

Имя Возраст
Сергей 21
Николай 22
Иван Петрович 48
Анюта ?

В каждой строке тут хранится пара величин. Имя мы будем называть ключом пары, а возраст — значением пары.

Весь набор таких пар и будет называться картойMap.

Ключом пары может быть что угодно, но у некоторых типов карт ключ не может быть null. Ключи должны быть уникальные: одна карта не может содержать два одинаковых ключа.


2. Класс HashMap

Класс HashMap является самой популярной коллекцией из всех карт (Map). С одной стороны, он очень похож на HashSet и имеет все его методы, а с другой — на список (ArrayList), если бы индексами у списка могли быть не числа, а слова.

Создать объект типа HashMap можно с помощью команды вида:

HashMap<TКлюч, TЗначение> имя = new HashMap<TКлюч, TЗначение>();

Где TКлюч — это тип ключей из пары элементов, TЗначение — тип значений в паре элементов, которые будут храниться в коллекции HashMap.

У класса HashMap есть такие методы:

Метод Описание
void put(ТКлюч key, ТЗначение value)
Добавляет в коллекцию пару (key, value)
ТЗначение get(ТКлюч key)
Возвращает значение по ключу.
boolean containsKey(ТКлюч key)
Проверяет наличие ключа в коллекции
boolean containsValue(ТЗначение value)
Проверяет наличие значения в коллекции
ТЗначение remove(ТКлюч key)
Удаляет элемент из коллекции
void clear()
Очищает коллекцию: удаляет все элементы
int size()
Возвращает количество пар элементов в коллекции
Set<ТКлюч> keySet()
Возвращает множество ключей коллекции
Collection<ТЗначение> values()
Возвращает множество элементов коллекции
Set<Map.Entry<TКлюч, TЗначение>> entrySet()
Возвращает все значения коллекции в виде множества (Set) пар (Map.Entry).

Добавление элементов в HashMap

Элементы добавляются в карту сразу парами: для этого используется метод put(). Первым в него передается ключ, вторым — значение.

HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("Серега", 21);
map.put("Николай", 22);
map.put("Иван Петрович", 48);
map.put("Анюта", null);

Если при добавлении элемента выяснится, что элемент с таким ключом уже есть, старое значение ключа заменится на новое.

Такое поведение делает HashMap похожим на массив или список, если бы у них в качестве индексов выступали слова (String), а не числа.

Важно:

В качестве Типа-Ключа и Типа-Значения могут выступать практически любые типы. Есть небольшие дополнительные требования к Типу-Ключу, но о них вы узнаете при детальном изучении коллекций в квесте Java Collections.



3. Подмножества HashMap: множество ключей

Допустим мы хотим просто вывести все элементы HashMap на экран, как нам это сделать? Для этого нужно понять, как пройтись по всем элементам HashMap. Это можно сделать разными способами.

Самый простой способ – использовать цикл по ключам

У элементов класса HashMap нет порядкового номера, поэтому цикл со счетчиком тут не подойдет. Зато мы можем получить множество ключей с помощью метода keySet(), а как пройтись по множеству вы уже знаете:

Код Описание
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("Серега", 21);
map.put("Николай", 22);
map.put("Иван Петрович", 48);
map.put("Анюта", null);

for (String key: map.keySet())
{
   Integer value = map.get(key);
   System.out.println(key + " --> " + value);
}






Цикл по всем ключам map

Получаем значение по ключу

Метод keySet() возвращает множество ключей. Можно использовать это множество двумя способами:

Компактная запись Длинная запись
for (String key: map.keySet())
{
   Integer value = map.get(key);
   System.out.println(key + " --> " + value);
}
Set<String> keys = map.keySet();

for (String key: keys)
{
   Integer value = map.get(key);
   System.out.println(key + " --> " + value);
}


4. Использование цикла по парам

Есть и более сложный способ: можно преобразовать Map в множество пар элементов, а потом использовать цикл по элементам множества, как мы уже раньше учили.

В коллекции HashMap есть вспомогательный класс для хранения пары элементов. Выглядит он примерно так:

class Entry<KeyType, ValueType>
{
   private KeyType key;
   private ValueType value;

   public KeyType getKey()
   {
      return this.key;
   }

   public ValueType getValue()
   {
      return this.value;
   }
}

Результат вызова метода entrySet() у объекта типа HashMap<ТКлюч, ТЗначение> будет иметь тип Set<Entry<ТКлюч, ТЗначение>>:

Set<Entry<Ключ, Значение>> имя = map.entrySet();

Тут мы видим сложный тип Set с параметром-значением, а в качестве параметра-значение выступает еще один сложный тип (Entry), так еще и с двумя параметрами.

Новичку очень легко в этом запутаться. Хотя, если разберетесь, сможете писать код вида:

HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("Серега", 21);
map.put("Николай", 22);
map.put("Иван Петрович", 48);
map.put("Анюта", null);

Set<Map.Entry<String, Integer>> entries = map.entrySet();
for(Map.Entry<String, Integer> pair: entries)
{
   String key = pair.getKey();
   Integer value = pair.getValue();
   System.out.println(key + " --> " + value);
}

Хотя этот код можно и немножко упростить:

Во-первых, можно не создавать отдельную переменную для entries, а сразу вызвать метод entrySet() внутри цикла for:

for(Map.Entry<String, Integer> pair: map.entrySet())
{
   String key = pair.getKey();
   Integer value = pair.getValue();
   System.out.println(key + " --> " + value);
}

Во-вторых, можно воспользоваться недавно появившимся оператором var для автоматического выведения типа пары элементов:

for(var pair: map.entrySet())
{
   String key = pair.getKey();
   Integer value = pair.getValue();
   System.out.println(key + " --> " + value);
}

Уже неплохо, да?



5. Сравнение ArrayList vs HashMap

HashMap сильно напоминает ArrayList, у которого в качестве индексов используются не цифры, а слова (или другой тип ключей).

А если в качестве ключа в HashMap использовать Integer, он становится еще более похожим на ArrayList. Сравните:

Код с ArrayList<String> Код с HashMap<Integer, String>
ArrayList<String> list = new ArrayList<String>();

list.add("Привет");
list.add("Hello");

String s = list.get(0);
list.set(0, s + "!");

for (String item: list)
{
   System.out.println(item);
}
HashMap<Integer, String> map = new HashMap<Integer, String>();

map.put(0, "Привет");
map.put(1, "Hello");

String s = map.get(0);
map.put(0, s + "!");

for (String item: map.values())
{
   System.out.println(item);
}