Map. Первое знакомство

Map. Первое знакомство

Дорогу осилит идущий

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

В целом, структура урока будет симметрична таковой в «Урок 41. Set. Первое знакомство».


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

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

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

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

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

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


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

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

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

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

Кроме методов 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 на основании статьи (пункт «SortedMap»): https://metanit.com/java/tutorial/5.9.php

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

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


Интерфейс 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 — рекомендую воспользоваться вложенным классом AbstractMapSimpleEntry. В том же классе можно найти и immutable аналог — вложенный класс SimpleImmutableEntry.

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

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


Класс HashMap

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

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


Класс LinkedHashMap

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

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

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


Класс TreeMap

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

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

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


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

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

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


Итог

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

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

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

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

Так держать!


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


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

Задача 1:

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

За основу предлагаю взять реализацию из задачи 3 урока 30.


Задача 2:

Реализуйте задачу из урока 19, используя Map. Реализацию выберите исходя из особенностей исходной задачи.


Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)

Канал: https://t.me/+relA0-qlUYAxZjI6

Мой тг: https://t.me/ironicMotherfucker

 

Дорогу осилит идущий!

Report Page