User Treefeed
Treefeed
21 уровень

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

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

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();
КОНЕЦ Оригинал
Комментарии (112)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ СДЕЛАТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
CondrCat Уровень 22, Красноярск, Россия
11 апреля 2021
Тут внятная статья по HashMap: тык
Мирослав Уровень 19, Тбилиси, Грузия
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 уровня это слишком ранова-то... (а именно на этом уровне одна из ссылок привела меня сюда). Сейчас статья написана на эльфийском, выгляди прикольно, но ничего не понятно. Перечитаю позже.
Никита Уровень 10, Пермь, Россия
17 октября 2020
Иногда мне кажется что они специально пишут статьи таким образом, чтобы захотелось как следует погуглить. Код специально поставлен неработающий, чтобы ты сам во всём разобрался. Работающий код (для "Упорядочивание Map по..."):

List<Map.Entry<String, String>> list = new ArrayList<>(createMap().entrySet());
        Collections.sort(list, new Comparator<>() {
            @Override
            public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        });
createMap() пишем отдельно. Либо можно вместо него вставить

Map.ofEntries(Map.entry("er","t"))
Nicha Уровень 26, Тольятти, Россия
6 августа 2020
Непонятная статья...
Гудини Уровень 23, Санкт-Петербург
27 июля 2020
Половина кода не работает, по крайней мере в java 8. Надо бы переписать в таком стиле. List<Map.Entry<String, String>> list = new ArrayList<>(map.entrySet()); list.sort(Map.Entry.comparingByValue());
Ansagan Islamgali Уровень 32, Казахстан
24 июля 2020

//лист ключей
List keyList = new ArrayList(Map.keySet());
//лист значений
List valueList = new ArrayList(Map.valueSet());
//лист ключ-значения
List entryList = new ArrayList(Map.entrySet());
Map путает в данном коде. я думал что это класс Map пока не открыл оригинал