Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions lessons/java-core/043/Introduction to Map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# Map. Первое знакомство

В рамках текущего урока мы познакомимся с последним из типов коллекций — **Map** (словарь, сленг. — **мапа**). Разберем
методы интерфейсов, посмотрим на реализации. Но знакомство с внутренней устройством этих реализаций будет вынесено в
отдельный урок — эта тема популярна на собеседованиях и стоит разобраться с ней подробно.

В целом, структура урока будет симметрична таковой в
[«Урок 41. Set. Первое знакомство»](https://telegra.ph/Set-Pervoe-znakomstvo-01-25).

## Общая информация

Все реализации `Map` являются ни чем иным, как **ассоциативным массивом**. Соответственно, любая мапа представляет собой
набор пар вида «ключ-значение», где ключ уникален, значение — нет.

Таким образом, если все изученные нами типы коллекций имели параметризацию вида `<E>`, то `Map` имеет параметризацию
вида `<K, V> (key, value)`.

Именно поэтому `Map`, в отличии от остальных типов коллекций, не является наследником `Collection` и `Iterable`. Строго
говоря, интерфейс `Map` вообще ни от кого не наследуется. Поэтому ряд методов, уже привычных по остальным коллекциям,
может отсутствовать.

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

Также реализации `Map` лежат в основе уже изученных реализаций Set'а.

## Интерфейсы Map, Map.Entry

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

В свою очередь `Entry` — вложенный интерфейс, является основной единичного значения `Map` — пары «ключ-значение». Грубо
говоря, любая мапа — набор элементов типа `Entry`.

Более подробно с методами этих интерфейсов предлагаю ознакомиться на основании
[статьи](https://metanit.com/java/tutorial/5.8.php) (до пункта _«Классы отображений. HashMap»_)

Кроме методов `Map`, описанных в статье, стоит также выделить несколько методов (групп методов), которые были опущены:

- Методы, параметры которых являются **функциональными интерфейсами**. После темы коллекций мы познакомимся с ФП в Java,
в т. ч. вернемся к этим методам. В `Collection` их было меньше, у `Map` — больше. В любом случае, работать с
коллекциями можно и без них;
- Методы `replace()`. Заменяют существующее значение для указанного ключа на новое. Существуют две перегруженные
версии — заменяющая значение, если существует ключ, а также заменяющая значение по ключу, лишь если старое значение
совпадает с указанным в соответствующем параметре.
- Статические методы создания мапы (или `Entry`). Нечто похожее мы видели в `List` и `Set`. Подробнее разберем эти
методы ниже.

Статические методы в `Map`:

- `of()`. В отличии от `List` и `Set`, в `Map` эти методы принимают параметрами ключи и значения — каждый нечетный
параметр будет `key`, следующий за ним четный — `value`. Существуют реализации `of()` от одной до десяти пар
«ключ-значение». Перегруженная версия этого метода для _varargs_ отсутствует в силу особенностей параметризации. Также
обратите внимание: передача в такие методы `null` приведет к исключению.
- Вместо `of()` с _varargs_ есть метод `ofEntries()`, принимающий массив `Map.Entry` (в виде _varargs_) и формирующий на
их основании мапу. Не самая удобная, но альтернатива. Также, как и в `of()`, _null_-значения недопустимы. Ни в
качестве параметров (Entry), ни в качестве ключей или значений этих `Entry`;
- `copyOf()` также существует, но, в отличии от известных нам реализаций, принимает параметром не `Collection`, а `Map`.
Логичный, но, почему-то не для всех очевидный нюанс;
- И, наконец, не имеющий аналогов метод `entry()`. Создает неизменяемый объект типа `Map.Entry` на основании переданных
параметров ключа и значения.

Для `Entry` в статье почему-то были опущены статические методы `comparingByKey()` и `comparingByValue()`. Каждый в двух
реализациях. Возвращают компараторы для сравнения `Entry` по ключу или значению соответственно. Как в естественном
порядке (в соответсвии с имплементацией `Comparable`, если она есть), так и на основании переданного параметром
компаратора.

Также был опущен статический метод `copyOf()`, создающий новое `Entry` на основании переданного параметром.

Работа с `Entry` напрямую — не самая лучшая практика, особенно за пределами **Stream API** (с ним мы познакомимся уже
достаточно скоро). Но иногда она является меньшим из зол. Поэтому лучше иметь общее представление о содержимом этого
интерфейса.

## Интерфейс SortedMap

Полагаю, на этом этапе вы уже догадываетесь, что иерархия наследования `Map` симметрична таковой у `Set`. Что, в целом,
логично, учитывая, что набор ключей мапы — то, на чем и строится основное взаимодействие с этим типом коллекций — легко
представить именно Set'ом.

Итак, предлагаю ознакомиться с методами `SortedMap` на основании [статьи](https://metanit.com/java/tutorial/5.9.php)
(пункт _«SortedMap»_).

Здесь все просто и, в целом, похоже на уже изученный `SortedSet`. Даже так же, как и в случае с `SortedSet`, в статье
почему-то забыт метод `comparator()`, возвращающий компаратор, на основании которого сделана сортировка. В `SortedSet` —
значений, здесь — ключей.

Далее, продолжая аналогию, разберем наследника `SortedMap` — `NavigableMap`.

## Интерфейс NavigableMap

Как вы, полагаю, догадались, ничего принципиально нового вы не увидите. `NavigableMap` по составу методов
напоминает `NavigableSet`, с поправкой на особенности параметризации, из-за чего ряд методов
(аналогичных `ceiling()`, `higher()`, `floor()` и `lower()` у `NavigableSet`) дублируются для `Entry` и для ключа.

[Пункт _«NavigableMap»_](https://metanit.com/java/tutorial/5.9.php)

## Класс AbstractMap.SimpleEntry и другие реализации Map.Entry

Прежде чем перейти к реализациям `Map`, предлагаю ознакомиться с основными реализациями `Map.Entry`. Благо, это не
займет много времени.

В случае, если вам понадобится создать изменяемую `Entry` — рекомендую воспользоваться вложенным
классом `AbstractMap` — `SimpleEntry`. В том же классе можно найти и immutable-аналог — вложенный
класс `SimpleImmutableEntry`.

Обе реализации имеют конструкторы, создающие `Entry` на основании пары «ключ-значение», а также на основании
другого `Entry`.

Альтернативой `AbstractMap.SimpleImmutableEntry` может выступить класс `KeyValueHolder` — именно его
использует `Map.entry()`. Из минусов — `KeyValueHolder` не имеет конструктора для создания объекта на базе
другого `Entry`, а также не является сериализуемым (что это значит — разберемся в свое время). В остальном эти
реализации идентичны.

## Класс HashMap

Наиболее простая и, одновременно, наиболее популярная реализация `Map`. Данные не отсортированы, порядок добавления не
сохраняется. В целом, как `HashSet`, только `HashMap`:)

Познакомиться с конструкторами и использованием можно в рамках [статьи](https://metanit.com/java/tutorial/5.8.php) (
пункт _«Классы отображений. HashMap»_)

## Класс LinkedHashMap

Реализация `Map`, сохраняющая порядок добавления элементов. Соответственно, методы `keySet()`, `values()` и `entrySet()`
будут возвращать коллекции, хранящие ключи/значения/Entry в соответствии с порядком добавления.

По аналогии с соответствующими реализациями `Set`, `LinkedHashMap` является наследником `HashMap`. Но, в отличии от
аналогичного Set'а, `LinkedHashMap` определяет ряд вложенных классов для реализации рассмотренных выше методов, а также
переопределяет ряд методов `HashMap`, что и позволяет гарантировать нужный порядок элементов.

К слову, порядок элементов в `LinkedHashSet` тоже гарантируется именно из-за использования `LinkedHashMap` внутри.

## Класс TreeMap

Единственная публичная реализация `NavigableMap` в `java.util`. Строится на основе уже знакомого нам _RB-tree_.

Как и соответствующий сет, хранит элементы в отсортированном виде (на основании `Comparable` или `Comparator` для
ключа).

С конструкторами и использованием предлагаю познакомиться в [статье](https://metanit.com/java/tutorial/5.9.php)
(пункт _«TreeMap»_)

## Другие реализации

Мы рассмотрели выше основные непотокобезопасные реализации в `java.util`. Из-за возможностей, которые дают ассоциативные
массивы, публичных реализаций, заточенных под узкую специфику использования, у `Map` больше, чем у `Set`. Но в рамках
данного урока мы их затрагивать не будем, ограничимся мапами общего назначения. Зато озвучим основные потокобезопасные
реализации:

- _legacy_: классической legacy-реализацией `Map` является `Hashtable`;
- _java.util.concurrent_: `ConcurrentHashMap` как потокобезопасный аналог `HashMap`. Реализацией `NavigableMap` и,
соответственно, аналогом `TreeMap` является `ConcurrentSkipListMap`.

## Итог

На данном этапе можно утверждать, что мы познакомились со всеми типами коллекций в Java.

Это еще далеко не конец знакомства с `Collection Framework` — впереди еще разбор устройства `HashMap`, знакомство с
коллекциями из `java.util.concurrent` в контексте изучения многопоточности, обработка коллекций в рамках ФП и разбор
методов коллекций, которые были опущены на текущем этапе.

Тем не менее, сейчас мы достигли определенной черты — текущий знаний хватит, чтобы написать полноценное однопоточное
приложение. Да, оно будет выглядеть несовременно. Да, оно вряд ли будет реализовано качественно в контексте архитектуры.
Да, это будет консольное десктоп-приложение. И да, оно сможет сохранять результат своей работы в лучшем случае в файл, а
не в БД. Но это уже результат и серьезное достижение.

Поэтому искренне поздравляю всех, кто дошел до этого этапа. Надеюсь, мне удалось сделать ваш путь менее тернистым, чем
он был у меня и многих других разработчиков. Дальше будет… по-разному. Где-то будет удобнее и проще, где-то будет
сложнее, где-то будет взрываться мозг. Но именно на данном этапе можно утверждать, что вы познакомились с необходимым
базисом, на котором строится дальнейшее развитие Java-разработчика.

Так держать!

#### С теорией на сегодня все!

![img.png](../../../commonmedia/defaultFooter.jpg)

Переходим к практике:

## Задача 1:

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

За основу предлагаю взять реализацию из
[задачи 3 урока 30](https://github.com/KFalcon2022/practical-tasks/tree/master/src/com/walking/lesson30_regex/task3).

## Задача 2:

Реализуйте
[задачу из урока 19](https://github.com/KFalcon2022/practical-tasks/tree/master/src/com/walking/lesson19_object_methods),
используя `Map`. Реализацию выберите исходя из особенностей исходной задачи.

> Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)
>
> Канал: https://t.me/ViamSupervadetVadens
>
> Мой тг: https://t.me/ironicMotherfucker
>
> **Дорогу осилит идущий!**