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

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

Вступление

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

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

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

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

Это третья часть серии статей о Java Collections:

Списки и наборы ограничений

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

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

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

Карты

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

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

 Map<String, Integer> wordsCount; 

Такая Map использует String качестве ключа и Integer качестве значения.

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

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

 Map<String, Integer> wordsCount = new HashMap<>(); 
 wordsCount.put("the", 153); 

Примечание. Помимо связывания значения с ключом, метод put() также возвращает ранее связанное значение, если оно есть, и null противном случае.

Но что, если мы хотим добавить элемент, только если с его ключом ничего не связано? Затем у нас есть несколько возможностей, первая из которых - проверить наличие ключа с помощью метода containsKey()

 if (!wordsCount.containsKey("the")) { 
 wordsCount.put("the", 150); 
 } 

Благодаря containsKey() мы можем проверить, связан ли уже элемент с ключом the и добавить значение, только если нет.

Однако это немного многословно, особенно учитывая, что есть два других варианта. Прежде всего, давайте посмотрим на самый древний метод putIfAbsent() :

 wordsCount.putIfAbsent("the", 150); 

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

Теперь давайте посмотрим на второй вариант. Начиная с Java 8, существует другой метод, похожий на putIfAbsent() - computeIfAbsent() .

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

Аргумент функции является ключом, если от него зависит создание значения. Итак, чтобы добиться того же результата, что и с предыдущими методами, нам нужно будет сделать:

 wordsCount.computeIfAbsent("the", key -> 3 + 150); 

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

Примечание. Этот метод особенно полезен, когда значение сложно создать или если метод вызывается часто, и мы хотим избежать создания слишком большого количества объектов.

Получение элементов

До сих пор мы учились размещать элементы на Map , но как насчет их получения?

Для этого мы используем метод get() :

 wordsCount.get("the"); 

Этот код вернет количество слов слова the .

Если ни одно значение не соответствует данному ключу, get() возвращает null . Однако мы можем избежать этого, используя метод getOrDefault() :

 wordsCount.getOrDefault("duck", 0); 

Примечание. Здесь, если с ключом ничего не связано, мы вернем 0 вместо null .

Теперь это для получения по одному элементу за раз, используя его ключ. Посмотрим, как получить все элементы. Интерфейс Map предлагает три метода для достижения этой цели:

  • entrySet() : возвращает Set Entry<K, V> который представляет собой пары ключ / значение, представляющие элементы карты.
  • keySet() : возвращает Set ключей карты
  • values() : возвращает Set значений карты

Удаление элементов

Теперь, когда мы знаем, как размещать и извлекать элементы из карты, давайте посмотрим, как удалить некоторые из них!

Сначала давайте посмотрим, как удалить элемент по его ключу. Для этого мы воспользуемся remove() , который принимает ключ в качестве параметра:

 wordsCount.remove("the"); 

Метод удалит элемент и вернет связанное значение, если таковое имеется, в противном случае он ничего не сделает и вернет null .

Метод remove() имеет перегруженную версию, принимающую также значение. Его цель - удалить запись, только если она имеет тот же ключ и значение, что и указанные в параметрах:

 wordsCount.remove("the", 153); 

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

Этот метод не возвращает Object , а возвращает boolean указывающее, был ли элемент удален или нет.

Итерация по элементам

Мы не можем говорить о коллекции Java, не объясняя, как ее перебирать. Мы увидим два способа перебора элементов Map .

Первый - это for-each , который мы можем использовать в entrySet() :

 for (Entry<String, Integer> wordCount: wordsCount.entrySet()) { 
 System.out.println(wordCount.getKey() + " appears " + wordCount.getValue() + " times"); 
 } 

До Java 8 это был стандартный способ перебора Map . К счастью для нас, в Java 8 появился менее подробный способ: метод forEach() который принимает BiConsumer<K, V> :

 wordsCount.forEach((word, count) -> System.out.println(word + " appears " + count + " times")); 

Поскольку некоторые могут быть не знакомы с функциональным интерфейсом, BiConsumer - он принимает два аргумента и не возвращает никакого значения. В нашем случае мы передаем word и его count , которые затем распечатываются с помощью лямбда-выражения.

Этот код очень лаконичен и его легче читать, чем предыдущий.

Проверка наличия элемента

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

Прежде всего, есть метод containsKey() , который мы уже использовали и который возвращает boolean значение, сообщающее нам, соответствует ли элемент заданному ключу или нет. Но есть также метод containsValue() который проверяет наличие определенного значения.

Давайте представим Map представляющую результаты игроков в игре и первой, кто выиграл 150 побед, тогда мы могли бы использовать метод containsValue() чтобы определить, выиграл ли игрок игру или нет:

 Map<String, Integer> 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!"); 

Получение размера и проверка на пустоту

Теперь, что касается List и Set , есть операции по подсчету количества элементов.

Эти операции - size() , который возвращает количество элементов Map , и isEmpty() , который возвращает boolean , указывающее, содержит ли Map какой-либо элемент или нет:

 Map<String, Integer> map = new HashMap<>(); 
 map.put("One", 1); 
 map.put("Two", 2); 
 
 System.out.println(map.size()); 
 System.out.println(map.isEmpty()); 

Результат:

 2 
 false 

SortedMap

Мы рассмотрели основные операции, которые мы можем реализовать на Map помощью реализации HashMap Но есть и другие унаследованные от него интерфейсы карты, которые предлагают новые функции и делают контракты более строгими.

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

Кроме того, этот интерфейс предлагает функции, использующие преимущества поддерживаемого порядка, такие как firstKey() и lastKey() .

Давайте повторно воспользуемся нашим первым примером, но на этот раз SortedMap

 SortedMap<String, Integer> 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 

Если вы хотите настроить критерии порядка, вы можете определить собственный Comparator в конструкторе TreeMap

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

 SortedMap<String, Integer> wordsCount = 
 new TreeMap<String, Integer>(new Comparator<String>() { 
 @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 интерфейса и добавляет методы , позволяющие перемещаться по карте более легко найти записи ниже или выше определенного ключа.

Например, метод lowerEntry() возвращает запись с наибольшим ключом, который строго меньше заданного ключа:

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

 SortedMap<String, Integer> wordsCount = new TreeMap<>(); 
 wordsCount.put("the", 150); 
 wordsCount.put("ball", 2); 
 wordsCount.put("duck", 4); 
 
 System.out.println(wordsCount.lowerEntry("duck")); 

Результатом будет:

 ball 

ConcurrentMap

Наконец, последнее Map мы рассмотрим, - это ConcurrentMap , которое делает контракт Map более строгим, обеспечивая его потокобезопасность, которая может использоваться в многопоточном контексте, не опасаясь, что содержимое карты будет быть непоследовательным.

Это достигается за счет синхронизации операций обновления, таких как put() и remove() .

Реализации

Теперь давайте посмотрим на реализации различных интерфейсов Map Мы не будем рассматривать их все, а только основные:

  • HashMap : это реализация, которую мы использовали чаще всего с самого начала, и она наиболее проста, поскольку предлагает простое сопоставление null ключами и значениями. Это прямая реализация Map и поэтому не гарантирует ни порядка элементов, ни потоковой безопасности.
  • EnumMap : реализация, которая принимает enum в качестве ключей карты. Следовательно, количество элементов в Map ограничено количеством констант enum . Кроме того, реализация оптимизирована для обработки довольно небольшого количества элементов, которые может содержать Map
  • TreeMap : В качестве реализации SortedMap и NavigableMap интерфейсов, TreeMap гарантирует , что элементы , добавленные к нему будет соблюдать определенный порядок (на основе ключа). Этот порядок будет либо естественным порядком ключей, либо тем, который обеспечивается Comparator мы можем передать конструктору TreeMap
  • ConcurrentHashMap : эта последняя реализация, скорее всего, такая же, как HashMap , ожидайте, что она обеспечивает безопасность потоков для операций обновления, что гарантируется интерфейсом ConcurrentMap

Заключение

Фреймворк Java Collections - это фундаментальный фреймворк, который должен знать каждый Java-разработчик.

В этой статье мы говорили об интерфейсе Map Мы рассмотрели основные операции с помощью HashMap а также несколько интересных расширений, таких как SortedMap или ConcurrentMap .

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus