Основы карты Java

В этой статье мы расскажем, как работает структура данных карты, как ее использовать в Java, а также узнаем, что нового в картах в Java 9.

Что такое карта?

Карта — это структура данных, предназначенная для быстрого поиска. Данные хранятся в парах ключ-значение, где каждый ключ уникален. Каждый ключ соответствует значению, отсюда и название. Эти пары называются элементами карты.

В JDK java.util.Map — это интерфейс, который включает сигнатуры методов для вставки, удаления и извлечения.

Каркас коллекций

Интерфейс Map реализуется рядом классов в Collections Framework. Каждый класс предлагает различную функциональность и безопасность потоков. Наиболее распространенной реализацией является HashMap, поэтому мы будем использовать ее для большинства наших примеров.

Map — единственная коллекция, которая не расширяет и не реализует интерфейс Collection. Он не подходит для того же контракта, поскольку ему нужно работать с парами, а не с отдельными значениями.

Создать карту

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

HashMap допускает один нулевой ключ и несколько нулевых значений. Он не сохраняет порядок элементов и не гарантирует, что порядок останется неизменным с течением времени.

Давайте создадим HashMap с целочисленными ключами и строковыми значениями:

Поскольку все карты реализуют интерфейс карты, следующие методы будут работать для любой реализации карты, показанной выше.

Добавить на карту

Метод put() позволяет нам вставлять записи в нашу карту. Для этого требуются два параметра: ключ и его значение.

Теперь давайте заполним нашу карту идентификаторами и именами:

Вот как выглядит наша карта сейчас:

1 -> Petyr Baelish 
2 -> Sansa Stark 
3 -> Jon Snow 
4 -> Jamie Lannister

Иногда нам может понадобиться добавить сразу несколько записей или объединить две карты. Для этого существует метод putAll(). Он копирует ссылки на записи с другой карты, чтобы заполнить нашу.

Дублирующиеся ключи

Ранее мы упоминали, что дубликаты ключей не допускаются на картах.

Давайте посмотрим, что происходит, когда мы пытаемся вставить уже существующий ключ:

Метод не жалуется, но обратите внимание, как новое значение перезаписало предыдущее:

1 -> Petyr Baelish 
2 -> Sansa Stark 
3 -> Jon Snow 
4 -> Daenerys Targaryen

Метод put возвращает предыдущее значение, если мы хотим его использовать. Если предыдущего значения не было, возвращается значение null.

Чтобы проверить, существует ли ключ, мы используем логический метод containsKey():

Точно так же метод containsValue() проверяет наличие значения:

Получить с карты

Метод get() принимает ключ и возвращает значение, связанное с этим ключом, или null, если значение отсутствует.

Удалить с карты

Метод remove() принимает ключ, чтобы найти запись для удаления. Он возвращает значение, связанное с удаленной записью, или ноль, если значение отсутствует.

1 -> Petyr Baelish 
2 -> Sansa Stark 
4 -> Daenerys Targaryen

Если мы хотим очистить карту, мы можем вызвать clear(). Это метод void, поэтому он ничего не возвращает.

Размер карты

Метод size() возвращает количество записей на нашей карте.

Метод isEmpty() возвращает логическое значение, указывающее, пуста карта или нет.

Просмотры коллекций

Интерфейс карты предоставляет методы просмотра коллекции, которые позволяют нам просматривать нашу карту с точки зрения типа коллекции. Эти представления предоставляют нам единственный механизм для перебора карты.

  • keySet() — возвращает набор ключей с карты
  • values() — возвращает коллекцию значений из карты
  • entrySet() — возвращает набор объектов Map.Entry, которые представляют пары ключ-значение на карте.

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

Представления коллекции поддерживают удаление записей, но не вставку.

набор ключей()

Метод keySet() возвращает набор ключей, содержащихся в нашей карте:

ценности()

Метод values() возвращает коллекцию значений, содержащихся в нашей карте:

Почему он возвращает коллекцию вместо набора? Потому что не гарантируется, что значения карты будут уникальными, поэтому набор не будет работать.

записьНабор()

Метод entrySet() используется для получения набора записей на нашей карте. Набор будет содержать объекты Map.Entry. Объект Map.Entry — это просто пара ключ-значение. Мы можем вызвать getKey() или getValue() для этого объекта.

Наиболее часто набор entrySet используется для создания циклов, которые мы рассмотрим в следующем разделе.

Итерация по карте

Есть много способов итерации по карте. Давайте рассмотрим некоторые распространенные подходы.

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

Использование foreach и Map.Entry

Это наиболее распространенный метод и предпочтительнее в большинстве случаев. Мы получаем доступ как к ключам, так и к значениям в цикле.

Использование лямбда-выражения Java 8

Сортировка карты по ключу

HashMap не сохраняет порядок в своих записях. Вот пример, который иллюстрирует этот момент:

Приведенный выше код выводит содержимое карты в случайном порядке:

key: 20, value: Beth 
key: 40, value: Lucy 
key: 10, value: Amir 
key: 30, value: Arnie

Если мы используем TreeMap, он автоматически отсортирует записи по ключу. Давайте преобразуем наш HashMap в TreeMap и распечатаем содержимое:

Вывод теперь отсортирован по ключу:

key: 10, value: Amir 
key: 20, value: Beth 
key: 30, value: Arnie 
key: 40, value: Lucy

Сортировка карты по значению

Это немного сложнее и требует, чтобы мы написали метод.

  • В строке 3 мы создаем список именованных записей, который содержит объекты Map.Entry из карты, которую мы передали в метод.
  • В строке 5 мы используем Collections.sort() и реализуем компаратор с помощью entry.getValue(), чтобы он знал, как сравнивать записи.
  • На этом этапе наш список, содержащий объекты входа, отсортирован по значению. Теперь нам нужно вернуть его на карту.
  • В строке 12 мы создаем LinkedHashMap, представляющую собой реализацию карты, которая сохраняет порядок вставки своих записей.
  • В строках 14–16 мы перебираем наш список и заполняем новую карту.

Давайте посмотрим на этот метод в действии:

Вывод теперь отсортирован по значению:

key: 1, value: Amir 
key: 3, value: Arnie 
key: 2, value: Beth 
key: 4, value: Lucy

Новое в Java 9

Одно из улучшений, внесенных Oracle в Java 9, — добавление синтаксического сахара для создания неизменяемых карт. Под неизменяемым мы подразумеваем, что он не может измениться — добавление или удаление записей не разрешено.

Вот как мы создаем неизменяемую карту в Java 8:

Это выглядит запутанно и неловко. Эти двойные скобки особенно необычны.

Java 9 представляет статический фабричный метод с именем Map.of(), чтобы сделать его немного чище.

Вот новый способ сделать это в Java 9:

Здесь следует предостеречь — новый фабричный метод не делает очевидным, что он создает неизменяемую карту. В нашем примере мы назвали переменную immutableMap, чтобы она была понятна другим разработчикам.

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

Первоначально опубликовано на www.codebyamir.com 28 июня 2017 г.