Зарегистрируйтесь, чтобы продолжить обучение

HashMap Java: Maps

Принцип организации Map в Java похож на то, как организованы списки:

  • В стандартную библиотеку входит интерфейс Map, который описывает основные методы для работы коллекции.
  • Там же находятся конкретные реализации этого интерфейса, которые отличаются производительностью и потребляемыми ресурсами. Среди них самая универсальная и распространенная реализация это HashMap.

Рассмотрим работу HashMap на примере задачи описания телефонного кода стран. В этой задаче мы хотим создать справочник, в котором каждой стране сопоставлен ее код, например, +1 для штатов. Ключом в этом справочнике будет название страны, значением - код.

var codes = new HashMap<String, Integer>();
codes.put("usa", 2); // добавляет значение
codes.put("usa", 1); // заменяет значение так как такой ключ уже существовал
codes.put("france", 33);
codes.put("germany", 49);

// Порядок в выводе не соответствует порядку добавления
// Так как в HashMap нет заданного порядка у ключей
System.out.println(codes); // => {usa=1, germany=49, france=33}

// Проверяем наличие ключа
codes.containsKey("Usa"); // false
codes.containsKey("usa"); // true

// Проверяем наличие значения
codes.containsValue(1); // true
codes.containsValue(3); // false
codes.get("usa"); // 1
codes.get("spain"); // null
codes.getOrDefault("spain", 1); // 1

codes.size(); // 3
codes.isEmpty(); // false

В этом примере мы создали пустой HashMap в котором ключ это строка, а значение - число. Синтаксис построен по аналогии со списками. Типы данных, которые хранятся внутри указываются между символами < и > через запятую. Первый тип относится к ключу, второй к данным. Какие методы мы использовали:

  • put() добавляет или изменяет значение ключа
  • containsKey() - проверяет наличие ключа в коллекции с учетом регистра
  • get() - возвращает элемент по имени ключа. Если ключ не найден, то возвращается null.
  • getOrDefault() - в отличие от get(), в случае если ключ не найден, возвращает значение по умолчанию переданное вторым параметром
  • size() возвращает количество ключей в коллекции
  • isEmpty() проверяет, является ли коллекция пустой

Кроме добавления ключей их можно удалять. Метод remove() удаляет ключ, если он существует:

var codes = new HashMap<String, Integer>();
codes.put("usa", 1);
codes.remove("usa");

Если у коллекции есть начальный набор элементов и он достаточно большой, то их можно указать сразу с помощью метода Map.of(). Единственное нужно учитывать, что Map.of() создает неизменяемую коллекцию, то есть его элементы можно использовать, но изменять саму коллекцию нельзя. Чтобы добавить возможность ее изменения, нужно выполнить ее преобразование в HashMap.

import java.util.HashMap;
import java.util.Map;

var map = Map.of("usa", 1, "england", 44);
var codes = new HashMap<String, Integer>(map);
codes.put("france", 33);
codes.put("germany", 49);
System.out.println(codes); // {england=44, france=33, usa=1, germany=49}

Как работают ключи

HashMap внутри себя реализован с помощью хеш-таблиц. Хеш-таблица это структура данных, которая позволяет хранить, обновлять и удалять пары ключ-значение в обычных массивах. Зачем это нужно? Дело в том, что не существует прямого способа хранить Map в памяти, для этого нужно придумывать какую-то систему упаковки данных в какую-то существующую структуру, например массив. Большая часть механики работы HashMap спрятана от программиста. Но есть один элемент, с которым придется столкнуться явно. Это Хеш-код (Hash Code).

Хеш-код это число, которое формируется для каждого ключа добавленного в HashMap. Оно используется для того, чтобы определить место в хеш-таблице для данной пары ключ-значение. Хеш-код формируется самим ключом. HashMap лишь вызывает метод hashCode(), который добавлен во все объекты Java. Пример со строками:

var name = "hexlet";
name.hashCode(); // -1220578016

Точно так же, метод hashCode() присутствует и у всех объектов, определенных пользователем. Однако его реализация по умолчанию не подходит для работы с HashMap. Она спроектирована так, чтобы возвращать различные числа для разных объектов с точки зрения адресации в памяти. При такой схеме каждый новый объект будет отличаться от других объектов. На практике же, даже разные объекты могут быть одинаковыми с точки зрения логики работы программы. Ярчайший пример этому строки. Две одинаковые строки как два разных объекта не равны друг другу, но у них одинаковый хеш-код и они равны друг другу, если использовать метод equals():

var name = new String("hexlet");
var name2 = new String("hexlet");

name == name2; // false

name.hashCode(); // -1220578016
name2.hashCode(); // -1220578016

name.equals(name2); // true

Если бы не такая реализация hashCode() в строках, то код ниже бы не работал, как мы ожидаем:

var codes = new HashMap<String, Integer>();
// Тут используются два объекта строки. Один в put(), другой в get()
codes.put("usa", 1);
codes.get("usa"); // 1

В случае самостоятельно создаваемых классов это означает то, что нужно определять свою реализацию hashCode(). Подробнее об этом мы поговорим, когда начнем глубже изучать ООП.


Дополнительные материалы

  1. Интерфейс Map
  2. Класс HashMap

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff