JavaRush /Java блог /Random UA /Відповіді на найпопулярніші питання про інтерфейс Map
Professor Hans Noodles
41 рівень

Відповіді на найпопулярніші питання про інтерфейс Map

Стаття з групи Random UA
Вітання! Сьогодні ми дамо відповіді на найпоширеніші питання про Map, але спочатку давай згадаємо, що це таке. Відповіді на найпопулярніші питання про інтерфейс Map - 1Map - це структура даних, яка містить набір пар "ключ-значення". За структурою даних нагадує словник, тому її часто так і називають. У той же час, Map є інтерфейсом , і в стандартному JDK містить основні реалізації: Hashmap, LinkedHashMap, Hashtable, TreeMap. Найпопулярніша реалізація — Hashmap, тому її використовуватимемо в наших прикладах. Ось так виглядає стандартне створення та заповнення карти:
Map<Integer, String> map = new HashMap<>();
map.put(1, "string 1");
map.put(2, "string 2");
map.put(3, "string 3");
А так – отримання значень за ключом:
String string1 = map.get(1);
String string2 = map.get(2);
String string3 = map.get(3);
Якщо все з вищесказаного зрозуміло, приступимо до наших відповідей на популярні питання!

0. Як перебрати всі значення Map

Перебір значень - найчастіша операція, яку ви виконуєте з картами. Усі пари (ключ-значення) зберігаються у внутрішньому інтерфейсі Map.Entry, щоб отримати їх, потрібно викликати метод entrySet(). Він повертає безліч (Set) пар, які можна перебрати в циклі:
for(Map.Entry<Integer, String> entry: map.entrySet()) {
   // get key
   Integer key = entry.getKey();
   // get value
   String value = entry.getValue();
}

Или используя итератор:
Iterator<Map.Entry<Integer, String>> itr = map.entrySet().iterator();
while(itr.hasNext()) {
   Map.Entry<Integer, String> entry =  itr.next();
   // get key
   Integer key = entry.getKey();
   // get value
   String value = entry.getValue();
}

1. Як конвертувати Map у List

Інтерфейс Map має 3 методи, які повертають перелік елементів:
  • keySet() - повертає безліч (Set) ключів;
  • values() - повертає колекцію (Collection) значень;
  • entrySet() - повертає безліч (Set) наборів "ключ-значення".
Якщо заглянути в конструктори класу ArrayList, можна побачити, що є конструктор з аргументом типу Collection. Оскільки Set є спадкоємцем Collection, результати всіх вищезгаданих методів можна передати в конструктор класу ArrayList. Таким чином, ми створимо нові списки і заповнимо їх значеннями з Map:
// key list
List<Integer> keyList = new ArrayList<>(map.keySet());
// value list
List<String> valueList = new ArrayList<>(map.values());
// key-value list
List<Map.Entry<Integer, String>> entryList = new ArrayList<>(map.entrySet());

2. Як відсортувати ключі карти

Сортування карт - теж досить часта операція в програмуванні. Зробити це можна кількома способами:
  1. Помістити Map.Entry до списку та відсортувати його за допомогою Comparator .

    У компараторі порівнюватимемо виключно ключі пар:

    List> list = new ArrayList(map.entrySet());
    Collections.sort(list, new Comparator<Map.Entry<Integer, String>>() {
       @Override
       public int compare(Map.Entry<Integer, String> o1, Map.Entry<Integer, String> o2) {
           return o1.getKey() - o2.getKey();
       }
    });
    

    Якщо розібрався з лямбдами, цей запис можна суттєво скоротити:

    Collections.sort(list, Comparator.comparingInt(Map.Entry::getKey));
  2. Використовувати SortedMap, а точніше, її реалізацію - TreeMap, Яка в конструкторі приймає Comparator. Цей компаратор буде застосовуватися до ключів карти, тому ключами повинні бути класи, що реалізують інтерфейс Comparable:

    SortedMap<Integer, String> sortedMap = new TreeMap<>(new Comparator<Integer>() {
       @Override
       public int compare(Integer o1, Integer o2) {
           return o1 - o2;
       }
    });

    І, звичайно, все можна переписати, використовуючи лямбди:

    SortedMap<Integer, String> sortedMap = new TreeMap<>(Comparator.comparingInt(o -> o));

    На відміну від першого способу, використовуючи SortedMap, ми завжди зберігатимемо дані у відсортованому вигляді.

3. Як відсортувати карти

Тут варто використовувати підхід, аналогічний першому для ключів – отримувати список значень та сортувати їх у списку:
List <Map.Entry<Integer, String>> valuesList = new ArrayList(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<Integer, String>>() {
   @Override
   public int compare(Map.Entry<Integer, String> o1, Map.Entry<Integer, String> o2) {
       return o1.getValue().compareTo(o2.getValue());
   }
});
І лямбда для цього виглядає так:
Collections.sort(list, Comparator.comparing(Map.Entry::getValue));

4. У чому різниця між HashMap, TreeMap і Hashtable

Як згадувалося раніше, існують три основні реалізації інтерфейсу Map. Кожна з них має свої особливості:
  1. Порядок елементів. HashMapі Hashtableне гарантують, що елементи зберігатимуться у порядку додавання. Крім того, вони не гарантують, що порядок елементів не змінюватиметься з часом. У свою чергу, TreeMapгарантує зберігання елементів у порядку додавання або відповідно до заданого компаратора.

  2. Допустимі значення. HashMapдозволяє мати ключ і значення null HashTable- ні. TreeMap може використовувати значення null, тільки якщо це дозволяє компаратор. Без використання компаратора (при зберіганні пар додавання) значення null не допускається.

  3. Синхронізація. Тільки HashTableсинхронізована, решта — ні. Якщо до карти не звертатимуться різні потоки, рекомендується використовувати HashMap замість HashTable.

І загальне порівняння реалізацій:
HashMap HashTable TreeMap
Упорядкованість елементів ні ні так
null як значення так ні та ні
Потокобезпека ні так ні
Алгоритмічна складність пошуку елементів O(1) O(1) O(log n)
Структура даних під капотом хеш-таблиця хеш-таблиця червоно-чорне дерево

5. Як створити двонаправлену карту

Іноді з'являється необхідність використовувати структуру даних, в якій і ключі, і значення будуть унікальними, тобто карта міститиме пари "ключ-ключ". Така структура даних дозволяє створити "інвертований перегляд/пошук" по карті. Тобто, ми можемо знайти ключ за його значенням. Цю структуру даних називають двонаправленою картою, яка, на жаль, не підтримується JDK. Але, на щастя, її реалізацію можна знайти у бібліотеках Apache Common Collections чи Guava. Там вона називається BidiMap та BiMap відповідно. Ці реалізації вводять обмеження на унікальність ключів та значень. Таким чином, виходять відносини one-to-one.

6. Як створити порожню Map

Створити порожню карту можна двома способами:
  1. Звичайна ініціалізація об'єкта:

    Map<Integer, String> emptyMap = new HashMap<>();
  2. Створення незмінної (immutable) порожньої карти:

    Map<Integer, String> emptyMap =  Collections.emptyMap();
При спробі додавання даних до такої карти ми отримаємо: UnsupportedOperationExceptionвиняток. У цій статті ми розглянули найчастіші питання, які могли б виникнути у тебе при використанні інтерфейсу Map.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ