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)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Кру
Уровень 22
22 января, 16:34
читала статью каждый день в течение недели, ничего не поняла ))) но спасибо, очень наверное полезно
Peter
Уровень 14
19 мая 2022, 23:09
... это позор, ..., но это случилось. ... Позор - это писать имя переменной с большой буквы с тем же именем интерфейса... Позор - это создавать объект и присваивать его интерфейсу... Позор - это не уметь даже нормально переписать текст с оригинала...
Алексей
Уровень 11
18 марта 2023, 05:28
имя переменной Map написано с большой буквы, потому что это final(константа). их пишут с заглавной.
Boldik Sultan
Уровень 34
25 мая 2023, 16:36
чел хотел выпендриться, но обосрался по всем пунктам)
Евгений N
Уровень 23
8 апреля 2022, 15:33
а что значит в п.2 compareTo ?
return e1.getKey().compareTo(e2.getKey());
🙁
Павел Соловьёв докер - механизатор в Порт
25 июня 2022, 03:47
compareTo(T o) документация Описание : сравнивает два объекта JavaRush статья
YesOn
Уровень 13
19 января 2022, 07:40
Сразу обратите внимание, статья ссылается на оригинал(на английском языке), в котором 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, 04:28
Тут внятная статья по HashMap: тык
Андрей Dungeon Master
4 мая 2021, 14:03
согласен!
Мирослав
Уровень 29
Expert
7 марта 2021, 19:30
Это как : for(Entry entry: Map.entrySet()) { //получить ключ K key = entry.getKey(); //получить значение V value = entry.getValue(); } объяснить ( в ИДА) ??
Константин
Уровень 17
6 декабря 2020, 13:22
Не самая лучшая лекция. С первого же обзаца, автор загоняет в положение, где ты сразу начинаешь плыть и не чего не понимать. Уже с этих слов "что означает K расширяет Comparable и V так же расширяет Comparable." Что это значит? Зачем вы даёте ссылку на "Обобщение" и не поясняете "расширяет Comparable"? Мы ж только учимся. А тут такая боль
Гордей
Уровень 37
12 ноября 2020, 18:35
Зачем везде вызывать методы следующим образом Map.entrySet()? Это только сбивает с толку, кажется, что вызывается просто статический метод у класса или интерфейса. Просто добавить одну дополнительную строчку:
Map<Integer, String> mapExample = new HashMap<>();
и везде подставлять mapExample, тогда стало бы уже понятнее
Гордей
Уровень 37
12 ноября 2020, 19:14
Похоже я не до конца понял. Сначала я думал, что автор статьи просто подразумевает, что вместо Map можно подставить объект любого класса, который реализует этот интерфейс: HashMap, TreeMap и тд. Однако всё еще более запутаннее и непонятнее. Судя по строчкам ниже автор создает коллекцию HashMap и присваивает её переменной Map, т.е Map - это всё-таки объект (переменная ссылающаяся на объект)
private static final Map Map;
  static {
    Map = new HashMap();
    Map.put(1, "one");
    Map.put(2, "two");
  }
Отсюда у меня два вопроса. Во-первых, зачем писать название переменной с БОЛЬШОЙ БУКВЫ??? Во-вторых, зачем давать название переменной в точности такое же как и тип переменной (Map<> Map), ведь это только путает читателя? Возможно я что-то недопонимаю, и автор сделал всё верно, но пока я вижу сложную статью, в которой всё еще более усложняется приведенными выше действиями.
ivasvi
Уровень 30
28 ноября 2020, 13:01
Статья - перевод. Ссылка на оригинал под статьей. В оригинале с названием переменной все нормально. В популярных комментах это уже давно выяснили. Удивляет, что за 6 лет так и не подправили статейку...
YesOn
Уровень 13
19 января 2022, 07:30
Люто плюсую насчёт одного и того же названия переменной и большого регистра буквы. Из-за этого в голове пошли сильные помехи в понимании происходящего😅
однако
Уровень 11
5 ноября 2020, 16:45
в первой врезке вместо List valueList = new ArrayList(Map.valueSet()); должно быть List valueList = new ArrayList(Map.values());
🦔 Виктор веду учебный тг-канал в t.me/Javangelion Expert
20 октября 2020, 17:32
Мне кажется, что для 8 уровня это слишком ранова-то... (а именно на этом уровне одна из ссылок привела меня сюда). Сейчас статья написана на эльфийском, выгляди прикольно, но ничего не понятно. Перечитаю позже.
Никита
Уровень 28
1 ноября 2021, 13:13
😂🤣согласен - Элвинский лес отдыхает в сравнении этой статьей
🦔 Виктор веду учебный тг-канал в t.me/Javangelion Expert
1 ноября 2021, 14:33
Эльвин форест, голдшир, вестернфолл, эх... За Альянс!