JavaRush /Java блог /Архив info.javarush /9 главных вопросов о Map в Java
Treefeed
21 уровень

9 главных вопросов о Map в Java

Статья из группы Архив info.javarush
Напомним, что Map — это структурированные данные, состоящие из набора пар ключ-значение, и каждый ключ может использоваться только один раз в одной Map. Эта тема раскрывает 9 основных вопросов об использовании Map в Java и её воплощённых (имплементированных) классах. Для простоты, я буду использовать в примерах обобщения. Потому, я буду писать просто Map, не конкретизируя спецификатор Map. Но вы можете полагать, что оба значения K и V сопоставимы, что означает K расширяет Comparable и V так же расширяет Comparable.9 главных вопросов о Map в Java - 1

0. Обращение Map в List

В Java, интерфейс Map предлагает три вида коллекций: набор ключей, набор значений и набор ключ-значение. Все они могут быть обращены в List при помощи конструктора или метода addAll(). Следующая вырезка кода демонстрирует как сделать ArrayList из Map.

//лист ключей
List keyList = new ArrayList(Map.keySet());
//лист значений
List valueList = new ArrayList(Map.valueSet());
//лист ключ-значения
List entryList = new ArrayList(Map.entrySet());

1. Пройтись по всем значениям в Map

Проход по каждой паре ключ-значение — самая базовая, основная процедура прохода по Map. В Java, каждая пара хранится в поле Map называемом Map.Entry. Map.entrySet() возвращает набор ключ-значений, потому самым эффективным способом пройтись по всем значениям Map будет:

for(Entry entry: Map.entrySet()) {
  //получить ключ
  K key = entry.getKey();
  //получить значение
  V value = entry.getValue();
}
Так же мы можем использовать Iterator, особенно в версиях младше JDK 1.5

Iterator itr = Map.entrySet().iterator();
while(itr.hasNext()) {
  Entry entry = itr.next();
  //получить ключ
  K key = entry.getKey();
  //получить значение
  V value = entry.getValue();
}

2. Упорядочивание Map по ключам

Упорядочивание Map по ключам ещё одна часто встречаемая процедура. Первый способ: добавить Map.Entry в список, и упорядочить с использованием компаратора, что сортирует по значениям.

List list = new ArrayList(Map.entrySet());
Collections.sort(list, new Comparator() {
 
  @Override
  public int compare(Entry e1, Entry e2) {
    return e1.getKey().compareTo(e2.getKey());
  }
});
Другой способ: использовать SortedMap, которая ко всему, ещё и выстраивает свои ключи по порядку. Но, все ключи при этом должны воплощать Comparable или приниматься компаратором. Один из имплементированных классов SortedMapTreeMap. Её конструктор принимает компаратор. Следующий код показывает как превратить обычную Map в упорядоченную.

SortedMap sortedMap = new TreeMap(new Comparator() {
 
  @Override
  public int compare(K k1, K k2) {
    return k1.compareTo(k2);
  }
 
});
sortedMap.putAll(Map);

3. Упорядочивание Map по значениям

Добавление Map в список и последующая сортировка работают и в данном случае, но нужно в этот раз брать Entry.getValue(). Код ниже почти такой же как и раньше.

List list = new ArrayList(Map.entrySet());
Collections.sort(list, new Comparator() {
 
  @Override
  public int compare(Entry e1, Entry e2) {
    return e1.getValue().compareTo(e2.getValue());
  }
 
});
Мы всё ещё можем использовать SortedMap в данном случае, но только если значения уникальны. В таком случае, вы можете обратить пару ключ-значение в значение-ключ. Это решение обладает строгим ограничением, и не рекомендуется мною.

4. Инициализация статической/неизменной Map

Когда вы желаете, что бы Map оставалась неизменной, хорошим способом будет скопировать оную в неизменяемую (immutable) Map. Такая защитная техника программирования поможет вам создать не только безопасную для использования, но и так же потокобезопасную Map. Для инициализации статической/неизменной Map, мы можем использовать инициализатор static (см. ниже). Проблема данного кода в том, что не смотря на объявление Map как static final, мы всё ещё можем работать с ней после инициализации, например Test.Map.put(3,"three");. Так что это не настоящая неизменность. Для создания неизменяемой Map с использованием статического инициализатора, нам нужен супер анонимный класс, который мы добавим в неизменяемую Map на последнем шаге инициализации. Пожалуйста, посмотрите на вторую часть кода. Когда будет выброшено UnsupportedOperationException, если вы запустите Test.Map.put(3,"three");.

public class Test {
 
  private static final Map Map;
  static {
    Map = new HashMap();
    Map.put(1, "one");
    Map.put(2, "two");
  }
}
public class Test {
 
  private static final Map Map;
  static {
    Map aMap = new HashMap();
    aMap.put(1, "one");
    aMap.put(2, "two");
    Map = Collections.unmodifiableMap(aMap);
  }
}
Библиотека Guava так же поддерживает различные способы инициализации статических и неизменных коллекций. Чтобы изучить подробнее преимущества утилиты Guava для неизменных коллекций, обратитесь к разделу Неизменные коллекции в Инструкции Guava.

5. Разница между HashMap, TreeMap, и Hashtable

Есть три основных воплощения интерфейса Map в Java: HashMap, TreeMap, и Hashtable. Главные отличия заключаются в следующем:
  • Порядок прохода. HashMap и HashTable не дают гарантий по упорядоченности в Map; в частности, они не гарантируют что порядок останется тем же самым в течении времени. Но TreeMap будет упорядочивать все значения в "естественном порядке" ключей или по компаратору.
  • Допустимые пары ключ-значение. HashMap позволяет иметь ключ null и значение null. HashTable не позволяет ключ null или значение null. Если TreeMap использует естественный порядок или компаратор не позволяет использовать ключ null, будет выброшено исключение.
  • Синхронизация. Только HashTable синхронизирована, остальные — нет. Но, "если потокобезопасное воплощение не нужно, рекомендуется использовать HashMap вместо HashTable".
Более подробное сравнение

.                       | HashMap | HashTable | TreeMap
-------------------------------------------------------

Упорядочивание          |нет      |нет        | да
null в ключ-значение    | да-да   | нет-нет   | нет-да
синхронизировано        | нет     | да        | нет
производительность      | O(1)    | O(1)      | O(log n)
воплощение              | корзины | корзины   | красно-чёрное дерево
Прочтите подробнее об отношениях HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap.

6. Map с реверсивным поиском/просмотром

Иногда, нам нужен набор пар ключ-ключ, что подразумевает значения так же уникальны, как и ключи (паттерн один-к-одному). Такое постоянство позволяет создать "инвертированный просмотр/поиск" по Map. То есть, мы можем найти ключ по его значению. Такая структура данных называется двунаправленная Map, которая к сожалению, не поддерживается JDK. Обе Apache Common Collections и Guava предлагают воплощение двунаправленной Map, называемые BidiMap и BiMap, соответственно. Обе вводят ограничение, которое задаёт соответствие 1:1 между ключами и значениями.

7. Поверхностная копия Map

Почти все, если не все, Map в Java содержат конструктор копирования другой Map. Но процедура копирования не синхронизирована. Что означает когда один поток копирует Map, другой может изменить её структуру. Для предотвращения внезапной рассинхронизации копирования, один из них должен использовать в таком случае Collections.synchronizedMap().

Map copiedMap = Collections.synchronizedMap(Map);
Другой интересный способ поверхностного копирования — использование метода clone(). Но он НЕ рекомендуется даже создателем фреймворка коллекций Java, Джошуа Блохом. В споре "Конструктор копирования против клонирования", он занимает позицию: Цитата: "Я часто привожу публичный метод clone в конкретных классах, поскольку люди ожидают их там увидеть. ... это позор, что Клонирование сломано, но это случилось. ... Клонирование это слабое место, и я думаю люди должны быть предупреждены о его ограничениях." По этой причине, я даже не показываю вам, как использовать метод clone() для копирования Map

8. Создание пустой Map

Если Map неизменна, используйте:

Map = Collections.emptyMap();
Или, используйте любое другое воплощение. Например:

Map = new HashMap();
КОНЕЦ
Комментарии (123)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Кру Уровень 21
22 января 2024
читала статью каждый день в течение недели, ничего не поняла ))) но спасибо, очень наверное полезно
Peter Уровень 14
19 мая 2022
... это позор, ..., но это случилось. ... Позор - это писать имя переменной с большой буквы с тем же именем интерфейса... Позор - это создавать объект и присваивать его интерфейсу... Позор - это не уметь даже нормально переписать текст с оригинала...
Евгений N Уровень 22
8 апреля 2022
а что значит в п.2 compareTo ?

return e1.getKey().compareTo(e2.getKey());
🙁
YesOn Уровень 13
19 января 2022
Сразу обратите внимание, статья ссылается на оригинал(на английском языке), в котором map пишут с маленькой буквы:

// key list
List keyList = new ArrayList(map.keySet());
// value list
List valueList = new ArrayList(map.values());
// key-value list
List entryList = new ArrayList(map.entrySet());
Возможно так будет восприниматься часть информации яснее. Также в статье есть и другие ошибки, будьте внимательны, читайте комментарии или лучше возьмите другую статью.
CondrCat Уровень 22
11 апреля 2021
Тут внятная статья по HashMap: тык
Мирослав Уровень 29 Expert
7 марта 2021
Это как : for(Entry entry: Map.entrySet()) { //получить ключ K key = entry.getKey(); //получить значение V value = entry.getValue(); } объяснить ( в ИДА) ??
Константин Уровень 17
6 декабря 2020
Не самая лучшая лекция. С первого же обзаца, автор загоняет в положение, где ты сразу начинаешь плыть и не чего не понимать. Уже с этих слов "что означает K расширяет Comparable и V так же расширяет Comparable." Что это значит? Зачем вы даёте ссылку на "Обобщение" и не поясняете "расширяет Comparable"? Мы ж только учимся. А тут такая боль
Гордей Уровень 37
12 ноября 2020
Зачем везде вызывать методы следующим образом Map.entrySet()? Это только сбивает с толку, кажется, что вызывается просто статический метод у класса или интерфейса. Просто добавить одну дополнительную строчку:

Map<Integer, String> mapExample = new HashMap<>();
и везде подставлять mapExample, тогда стало бы уже понятнее
однако Уровень 11
5 ноября 2020
в первой врезке вместо List valueList = new ArrayList(Map.valueSet()); должно быть List valueList = new ArrayList(Map.values());
🦔 Виктор Уровень 20 Expert
20 октября 2020
Мне кажется, что для 8 уровня это слишком ранова-то... (а именно на этом уровне одна из ссылок привела меня сюда). Сейчас статья написана на эльфийском, выгляди прикольно, но ничего не понятно. Перечитаю позже.