Рубрики
Без рубрики

Коллекции Java: Интерфейс Карты

Фреймворк Java Collections – это фундаментальная и необходимая структура. Интерфейс карты-это интерфейс высокого уровня, благодаря которому многие коллекции расширяют функциональность.

Автор оригинала: François Dupire.

Вступление

Фреймворк Java Collections – это фундаментальная и необходимая структура, которую любой сильный разработчик Java должен знать как свои пять пальцев.

Коллекция в Java определяется как группа или коллекция отдельных объектов, которые действуют как единый объект.

В Java существует множество классов коллекций, и все они расширяют java.util.Коллекция и java.util.Карта интерфейсы. Эти классы в основном предлагают различные способы формирования коллекции объектов внутри одного объекта.

Коллекции Java – это платформа, которая обеспечивает множество операций над коллекцией- поиск, сортировку, вставку, манипулирование, удаление и т.д.

Это третья часть серии статей о коллекциях Java:

  • Интерфейс Списка
  • Установленный Интерфейс
  • Интерфейс карты ( вы здесь )
  • Интерфейсы очереди и Deque

Перечисляет и устанавливает ограничения

Прежде всего, давайте обсудим ограничения List и Set . Они предоставляют множество функций для добавления, удаления и проверки наличия элементов, а также механизмы итерации. Но когда дело доходит до извлечения определенных предметов, они не очень удобны.

Интерфейс Set не предоставляет никаких средств для извлечения определенного объекта, так как он неупорядочен. А интерфейс List просто предоставляет возможность извлекать элементы по их индексу.

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

Карты

Вот где находится java.util.Отобразится интерфейс Map . Карта связывает элементы с ключами, позволяя нам извлекать элементы по этим ключам. Такие ассоциации имеют гораздо больше смысла, чем привязка индекса к элементу.

Карта – это универсальный интерфейс с двумя типами, один для ключей и один для значений. Поэтому, если бы мы хотели объявить Карту сохранение количества слов в тексте, мы бы написали:

Map wordsCount;

Такая Карта использует Строку в качестве ключа и Целое число в качестве значения.

Добавление Элементов

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

Map wordsCount = 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 (Entry wordCount: 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 () , чтобы определить, выигрывает игрок в игре или нет:

Map playersScores = 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() , который возвращает логическое значение , указывающее, содержит ли Карта какой-либо элемент или нет:

Map map = 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 :

SortedMap wordsCount = 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

Если вы хотите настроить критерии заказа, вы можете определить пользовательский Компаратор в конструкторе Древовидной карты|/.

Определив Компаратор , мы можем сравнивать ключи (не полные записи карты) и сортировать их на основе них, а не значений:

SortedMap wordsCount =
    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() возвращает запись с наибольшим ключом, который строго меньше заданного ключа:

Взяв карту из предыдущего примера:

SortedMap wordsCount = 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 .