Автор оригинала: François Dupire.
Вступление
Фреймворк Java Collections – это фундаментальная и необходимая структура, которую любой сильный разработчик Java должен знать как свои пять пальцев.
Коллекция в Java определяется как группа или коллекция отдельных объектов, которые действуют как единый объект.
В Java существует множество классов коллекций, и все они расширяют java.util.Коллекция
и java.util.Карта
интерфейсы. Эти классы в основном предлагают различные способы формирования коллекции объектов внутри одного объекта.
Коллекции Java – это платформа, которая обеспечивает множество операций над коллекцией- поиск, сортировку, вставку, манипулирование, удаление и т.д.
Это третья часть серии статей о коллекциях Java:
- Интерфейс Списка
- Установленный Интерфейс
- Интерфейс карты ( вы здесь )
- Интерфейсы очереди и Deque
Перечисляет и устанавливает ограничения
Прежде всего, давайте обсудим ограничения List
и Set
. Они предоставляют множество функций для добавления, удаления и проверки наличия элементов, а также механизмы итерации. Но когда дело доходит до извлечения определенных предметов, они не очень удобны.
Интерфейс Set
не предоставляет никаких средств для извлечения определенного объекта, так как он неупорядочен. А интерфейс List
просто предоставляет возможность извлекать элементы по их индексу.
К сожалению, индексы не всегда говорят сами за себя и поэтому имеют мало значения.
Карты
Вот где находится java.util.Отобразится интерфейс Map
. Карта
связывает элементы с ключами, позволяя нам извлекать элементы по этим ключам. Такие ассоциации имеют гораздо больше смысла, чем привязка индекса к элементу.
Карта
– это универсальный интерфейс с двумя типами, один для ключей и один для значений. Поэтому, если бы мы хотели объявить Карту
сохранение количества слов в тексте, мы бы написали:
MapwordsCount;
Такая Карта
использует Строку
в качестве ключа и Целое число
в качестве значения.
Добавление Элементов
Давайте теперь погрузимся в операции Карта
, начиная с добавления элементов. Существует несколько способов добавления элементов в карту
, наиболее распространенным из которых является метод put()
:
MapwordsCount = new HashMap<>(); wordsCount.put("the", 153);
Примечание: В дополнение к связыванию значения с ключом, метод put()
также возвращает ранее связанное значение, если таковое имеется, и null
в противном случае.
Но что, если мы хотим добавить элемент только в том случае, если с его ключом ничего не связано? Тогда у нас есть несколько возможностей, первая из которых-проверить наличие ключа с помощью метода containsKey()
:
if (!wordsCount.containsKey("the")) { wordsCount.put("the", 150); }
Благодаря методу containsKey()
мы можем проверить, связан ли элемент уже с ключом , и добавить значение только в том случае, если это не так.
Однако это немного многословно, особенно учитывая, что есть два других варианта. Прежде всего, давайте рассмотрим самый древний из них, метод putIfAbsent()
:
wordsCount.putIfAbsent("the", 150);
Этот вызов метода дает тот же результат, что и предыдущий, но с использованием только одной строки.
Теперь давайте рассмотрим второй вариант. Начиная с Java 8, существует другой метод, аналогичный putIfAbsent ()
, – computeIfAbsent()
.
Он работает примерно так же, как и первый, но принимает лямбда-функцию вместо прямого значения, что дает нам возможность создать экземпляр значения, только если к ключу еще ничего не прикреплено.
Аргумент функции является ключевым, если от него зависит создание экземпляра значения. Итак, чтобы достичь того же результата, что и с помощью предыдущих методов, нам пришлось бы сделать:
wordsCount.computeIfAbsent("the", key -> 3 + 150);
Он даст тот же результат, что и раньше, только он не будет вычислять значение 153, если другое значение уже связано с ключом //.
Примечание: Этот метод особенно полезен, когда значение слишком велико для создания экземпляра или если метод вызывается часто и мы хотим избежать создания слишком большого количества объектов.
Извлечение Элементов
До сих пор мы учились помещать элементы в Карту
, но как насчет их извлечения?
Для достижения этой цели мы используем метод get()
:
wordsCount.get("the");
Этот код вернет количество слов в слове |/.
Если ни одно значение не соответствует данному ключу, то get()
возвращает null
. Однако мы можем избежать этого, используя метод getOrDefault()
:
wordsCount.getOrDefault("duck", 0);
Примечание: Здесь, если с ключом ничего не связано, мы получим 0
назад вместо null
.
Теперь это для извлечения одного элемента за раз, используя его ключ. Давайте посмотрим, как извлечь все элементы. Интерфейс Map
предлагает три метода для достижения этой цели:
entrySet()
: ВозвращаетНабор
изЗаписи V>
, которые являются парами ключ/значение, представляющими элементы карты V>, которые являются парами ключ/значение, представляющими элементы карты
Набор ключей(): Возвращает
Наборключей карты
значения(): Возвращает
Набор
Удаление Элементов
Теперь, когда мы знаем, как помещать и извлекать элементы с карты, давайте посмотрим, как их удалить!
Сначала давайте посмотрим, как удалить элемент по его ключу. С этой целью мы будем использовать метод remove ()
, который принимает ключ в качестве параметра:
wordsCount.remove("the");
Метод удалит элемент и вернет соответствующее значение, если таковое имеется, в противном случае он ничего не делает и возвращает null
.
Метод remove()
имеет перегруженную версию, которая также принимает значение. Его цель состоит в том, чтобы удалить запись, только если она имеет тот же ключ и значение, что и указанные в параметрах:
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
wordsCount.remove("the", 153);
Этот вызов удалит запись, связанную со словом /
/, только если соответствующее значение равно 153 , иначе это ничего не даст.
Этот метод не возвращает Объект
, а скорее возвращает логическое значение
, указывающее, был ли элемент удален или нет.
Перебор элементов
Мы не можем говорить о коллекции Java, не объяснив, как ее перебирать. Мы увидим два способа перебора элементов карты
.
Первый-это цикл для каждого
, который мы можем использовать в методе entrySet()
:
for (EntrywordCount: wordsCount.entrySet()) { System.out.println(wordCount.getKey() + " appears " + wordCount.getValue() + " times"); }
До Java 8 это был стандартный способ перебора карты
. К счастью для нас, в Java 8 был введен менее подробный способ: метод forEach ()
, который принимает двоичный номер V>
: V>
wordsCount.forEach((word, count) -> System.out.println(word + " appears " + count + " times"));
Поскольку некоторые могут быть не знакомы с функциональным интерфейсом, Двоичный пользователь
– он принимает два аргумента и не возвращает никакого значения. В нашем случае мы передаем слово
и его количество
, которые затем выводятся с помощью лямбда-выражения.
Этот код очень лаконичен и его легче читать, чем предыдущий.
Проверка наличия элемента
Хотя у нас уже был обзор того , как проверить наличие элемента в Карте
, давайте поговорим о возможных способах достижения этого.
Прежде всего, существует метод containsKey ()
, который мы уже использовали и который возвращает логическое
значение, указывающее нам, соответствует ли элемент данному ключу или нет. Но существует также метод containsValue ()
, который проверяет наличие определенного значения.
Давайте представим Карту
, представляющую результаты игроков за игру и первого, кто одержит 150 побед, тогда мы могли бы использовать метод containsValue ()
, чтобы определить, выигрывает игрок в игре или нет:
MapplayersScores = new HashMap<>(); playersScores.put("James", 0); playersScores.put("John", 0); while (!playersScores.containsValue(150)) { // Game taking place } System.out.println("We have a winner!");
Получение размера и проверка пустоты
Теперь, что касается Списка
и Набора
, существуют операции для подсчета количества элементов.
Этими операциями являются size()
, который возвращает количество элементов Карты
, и isEmpty()
, который возвращает логическое значение
, указывающее, содержит ли Карта
какой-либо элемент или нет:
Mapmap = new HashMap<>(); map.put("One", 1); map.put("Two", 2); System.out.println(map.size()); System.out.println(map.isEmpty());
На выходе получается:
2 false
Карта сортировки
Теперь мы рассмотрели основные операции, которые мы можем реализовать на карте
с помощью реализации HashMap
. Но есть и другие интерфейсы карт, унаследованные от него, которые предлагают новые функции и делают контракты более строгими.
Первое, о чем мы узнаем, – это интерфейс SortedMap
, который гарантирует, что записи карты будут поддерживать определенный порядок на основе ее ключей.
Кроме того, этот интерфейс предлагает функции, использующие преимущества поддерживаемого порядка, такие как методы FirstKey()
и lastKey ()
.
Давайте повторим наш первый пример, но на этот раз с помощью SortedMap
:
SortedMapwordsCount = new TreeMap<>(); wordsCount.put("the", 150); wordsCount.put("ball", 2); wordsCount.put("duck", 4); System.out.println(wordsCount.firstKey()); System.out.println(wordsCount.lastKey());
Поскольку порядок по умолчанию является естественным, это приведет к следующему результату:
ball the
Если вы хотите настроить критерии заказа, вы можете определить пользовательский Компаратор
в конструкторе Древовидной карты|/.
Определив Компаратор
, мы можем сравнивать ключи (не полные записи карты) и сортировать их на основе них, а не значений:
SortedMapwordsCount = new TreeMap (new Comparator () { @Override public int compare(String e1, String e2) { return e2.compareTo(e1); } }); wordsCount.put("the", 150); wordsCount.put("ball", 2); wordsCount.put("duck", 4); System.out.println(wordsCount.firstKey()); System.out.println(wordsCount.lastKey());
Поскольку порядок изменен, вывод теперь:
the ball
Навигационная карта
Интерфейс NavigableMap
является расширением интерфейса SortedMap
и добавляет методы, позволяющие легче перемещаться по карте, находя записи ниже или выше определенного ключа.
Например, метод lower Entry()
возвращает запись с наибольшим ключом, который строго меньше заданного ключа:
Взяв карту из предыдущего примера:
SortedMapwordsCount = new TreeMap<>(); wordsCount.put("the", 150); wordsCount.put("ball", 2); wordsCount.put("duck", 4); System.out.println(wordsCount.lowerEntry("duck"));
Результатом будет:
ball
Карта параллелизма
Наконец, последнее расширение Map
, которое мы рассмотрим,-это ConcurrentMap
, которое делает контракт интерфейса Map
более строгим, обеспечивая его потокобезопасность, то есть его можно использовать в многопоточном контексте, не опасаясь, что содержимое карты будет несогласованным.
Это достигается путем выполнения операций обновления, таких как put()
и remove()
, synchronized .
Реализации
Теперь давайте рассмотрим реализации различных интерфейсов Map
. Мы не будем охватывать все из них, только основные:
HashMap
: Это реализация, которую мы использовали чаще всего с самого начала, и она наиболее проста, поскольку предлагает простое сопоставление ключа/значения, даже сnull
ключами и значениями. Это прямая реализацияMap
и, следовательно, не обеспечивает ни порядка элементов, ни потокобезопасности.EnumMap
: Реализация, которая принимаетперечисление
констант в качестве ключей карты. Следовательно, количество элементов вКарте
ограничено числом константперечисления
. Кроме того, реализация оптимизирована для обработки, как правило, довольно небольшого количества элементов, которые будет содержать такаяКарта
.Карта деревьев
: Как реализация интерфейсовSortedMap
инавигационной карты
,Карта деревьев
гарантирует, что добавленные в нее элементы будут соблюдать определенный порядок (в зависимости от ключа). Этот порядок будет либо естественным порядком ключей, либо тем, который обеспечиваетсяКомпаратором
, который мы можем предоставить конструкторуTreeMap
.ConcurrentHashMap
: Эта последняя реализация , скорее всего, совпадает сHashMap
, ожидайте, что она обеспечит потокобезопасность для операций обновления, как это гарантируется интерфейсомConcurrentMap
.
Вывод
Структура коллекций Java – это фундаментальная структура, которую должен знать каждый разработчик Java, как использовать.
В этой статье мы говорили об интерфейсе Map
. Мы рассмотрели основные операции с помощью HashMap
, а также несколько интересных расширений, таких как SortedMap
или ConcurrentMap
.