JavaRush /Java блог /Java Developer /HashMap у Java — що за карта така?
Автор
Milan Vucic
Репетитор по программированию в Codementor.io

HashMap у Java — що за карта така?

Стаття з групи Java Developer
Привіт! Сьогодні ми поговоримо про ще одну структуру даних – Map. Її офіційна українська назва – "асоціативний масив", але її використовують нечасто. Більш поширені варіанти "словник", "карта", або (найчастіше) – сленговий англіцизм "мапа" :) Усередині Map дані зберігаються у форматі "ключ"-"значення", тобто за парами. І як ключі, і як значення можуть виступати будь-які об'єкти – числа, рядки або об'єкти інших класів.

Відмінність Map від інших структур даних

Раніше ми розбирали структури даних, де елементи зберігаються само собою. У масиві, або списку ArrayList/LinkedList ми зберігаємо деяку кількість елементів. Але що, якщо наше завдання трохи зміниться? Наприклад, уяви собі, що перед нами стоїть завдання: створити список зі 100 осіб, де зберігатиметься ПІБ людини і номер її паспорта. В принципі, це не так складно. Наприклад, можна вмістити і те, і інше в рядок, і створити список ось таких рядків: "Анна Іванівна Решетнікова, 4211 717171". Але таке рішення має відразу два недоліки. По-перше, нам може знадобитися функція пошуку за паспортом. А за такого формату зберігання інформації це буде проблематично. А по-друге, ніщо не завадить нам створити двох різних людей з однаковими номерами паспорта. І це найсерйозніший недолік нашого рішення. Такі ситуації мають бути повністю виключені, не буває двох людей з однаковим номером паспорта. Тут на допомогу нам приходить Map і її заявлені особливості (зберігання даних за парою у форматі "ключ"-"значення"). Давай розглянемо найпоширенішу реалізацію Map – Java клас HashMap. HashMap – що за карта така? - 1

Створення HashMap у Java та робота з класом

Створюється ця реалізація дуже просто:

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

} 
Тут ми створили словник, у якому елементи зберігатимуться у форматі "число-рядок". Число виступатиме ключем, а рядок – значенням. Також ми вказали якого типу у нас будуть ключі (Integer, а якого – значення (String). Чому саме так? По-перше, ключ у HashMap завжди є унікальним. Для нас це чудово підійде, оскільки ми зможемо використовувати номер паспорта як ключ і уникнути повторів. А рядок із ПІБ буде виступати значенням (ПІБ у різних людей легко можуть повторюватися, у цьому нічого страшного для нас немає).

Додавання нової пари в HashMap

Це завдання має такий вигляд:

public class Main { 

   public static void main(String[] args) { 
       HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

 
       passportsAndNames.put(212133, "Лідія Аркадіївна Бублікова"); 
       passportsAndNames.put(162348, "Іван Михайлович Серебряков"); 
       passportsAndNames.put(8082771, "Дональд Джон Трамп"); 
       System.out.println(passportsAndNames); 

   } 

}
Для цього використовується метод put(). Крім того, HashMap має перевизначений метод toString(), тому її можна виводити в консоль. Виведення виглядатиме так:
{212133=Лідія Аркадіївна Бублікова, 8082771=Дональд Джон Трамп, 162348=Іван Михайлович Серебряков}

Особливості ключів HashMap

Тепер давай перевіримо, чи дійсно ключі є унікальними? Спробуємо додати новий елемент із уже наявним у мапі ключем:

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лідія Аркадіївна Бублікова"); 
   passportsAndNames.put(162348, "Іван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 
   passportsAndNames.put(162348, "Віктор Михайлович Стичкін");//повторний ключ 

   System.out.println(passportsAndNames); 

}
Виведення:
{212133=Лідія Аркадіївна Бублікова, 8082771=Дональд Джон Трамп, 162348=Віктор Михайлович Стичкін}
Попередній елемент із ключем 162348, як бачиш, було перезаписано. "Ключ" назвали ключем не просто так. Доступ до значень у HashMap здійснюється за ключем (але аж ніяк не навпаки – ключ не можна отримати за значенням, адже значення можуть бути повторюваними). Це добре видно на прикладах отримання елемента, а також видалення елемента з HashMap:

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лідія Аркадіївна Бублікова"); 
   passportsAndNames.put(162348, "Іван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 

   String lidiaName = passportsAndNames.get(212133); 
   System.out.println(lidiaName); 


   passportsAndNames.remove(162348); 
   System.out.println(passportsAndNames); 

}
Для того, щоб отримати значення або видалити пару зі словника, ми маємо передати в методи get() и remove() саме унікальний ключ, що відповідає цьому значенню. Номерних індексів, як у масивах або списках, у HashMap немає – доступ до значення здійснюється за ключем. Виведення в консоль:
Лідія Аркадіївна Бублікова {212133=Лідія Аркадіївна Бублікова, 8082771=Дональд Джон Трамп}

Перевірка наявності ключа і значення

У класах ArrayList і LinkedList ми могли перевірити, чи міститься в списку якийсь конкретний елемент. HashMap теж дає змогу це робити, причому для обох частин пари: у неї є методи containsKey()(перевіряє наявність якогось ключа) і containsValue() (перевіряє наявність значення).

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лідія Аркадіївна Бублікова"); 
   passportsAndNames.put(162348, "Іван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 


   System.out.println(passportsAndNames.containsKey(11111)); 
   System.out.println(passportsAndNames.containsValue("Дональд Джон Трамп")); 

}
Виведення:
false true

Отримання списку всіх ключів і значень

Ще одна зручна особливість HashMap – можна окремо отримати список усіх ключів і всіх значень. Для цього використовуються методи keySet() та values():

public class Main { 
 
   public static void main(String[] args) { 

       HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

       passportsAndNames.put(212133, "Лідія Аркадіївна Бублікова"); 
       passportsAndNames.put(162348, "Іван Михайлович Серебряков"); 
       passportsAndNames.put(8082771, "Дональд Джон Трамп"); 

       Set<Integer> keys = passportsAndNames.keySet(); 
       System.out.println("Ключі: " + keys); 

       ArrayList<String> values = new ArrayList<>(passportsAndNames.values()); 
       System.out.println("Значення: " + values); 

   } 

}
Ключі витягуються в колекцію Set. Її особливість у тому, що в ній не може бути елементів, що повторюються. Зараз головне запам'ятай, що список усіх ключів можна винести з HashMap в окрему колекцію. Значення ми в прикладі зберегли у звичайний ArrayList. Виведення в консоль:
Ключі: [212133, 8082771, 162348] Значення: [Лідія Аркадіївна Бублікова, Дональд Джон Трамп, Іван Михайлович Серебряков]
Методи size() та clear() роблять рівно те саме, що і в попередніх структурах, які ми проходили: перший – повертає кількість елементів у словнику на поточний момент, другий – видаляє всі елементи.

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лідія Аркадіївна Бублікова"); 
   passportsAndNames.put(162348, "Іван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 

   System.out.println(passportsAndNames.size()); 
   passportsAndNames.clear(); 
   System.out.println(passportsAndNames); 

}
Виведення:
3 {}
Для перевірки того, чи є в нашій HashMap хоча б один елемент, можна використовувати метод isEmpty():

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 

   passportsAndNames.put(212133, "Лідія Аркадіївна Бублікова"); 
   passportsAndNames.put(162348, "Іван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 
 
   if (!passportsAndNames.isEmpty()) { 

       System.out.println(passportsAndNames); 

   } 

}
Виведення:
{212133=Лідія Аркадіївна Бублікова, 8082771=Дональд Джон Трамп, 162348=Іван Михайлович Серебряков}
Тепер виведення в консоль у нас буде здійснюватися тільки після попередньої перевірки:)

Об'єднання двох мап в одну

Ще один цікавий момент – дві мапи можна об'єднати в одну. Для цього існує метод putAll(). Ми викликаємо його у першої HashMap, передаємо другу як аргумент, і елементи з другої будуть додані в першу:

public static void main(String[] args) { 

   HashMap<Integer, String> passportsAndNames = new HashMap<>(); 
   HashMap<Integer, String> passportsAndNames2 = new HashMap<>(); 
 
   passportsAndNames.put(212133, "Лідія Аркадіївна Бублікова"); 
   passportsAndNames.put(162348, "Іван Михайлович Серебряков"); 
   passportsAndNames.put(8082771, "Дональд Джон Трамп"); 
 
   passportsAndNames2.put(917352, "Олексій Андрійович Єрмаков"); 
   passportsAndNames2.put(925648, "Максим Олегович Архаров"); 


   passportsAndNames.putAll(passportsAndNames2); 
   System.out.println(passportsAndNames); 

}
Виведення:
{917352=Олексій Андрійович Єрмаков, 212133=Лідія Аркадіївна Бублікова, 8082771=Дональд Джон Трамп, 925648=Максим Олегович Архаров, 162348=Іван Михайлович Серебряков}
Усі елементи passportsAndNames2 були скопійовані в passportsAndNames. Тепер розглянемо приклад складніший. А саме – перебір HashMap у циклі.

for (Map.Entry<integer, mem-invalid-attributes-holder=string>
 entry: passportsAndNames.entrySet()) { System.out.println(entry); } 
Інтерфейс Map.Entry позначає саме пару "ключ-значення" всередині словника. Метод entrySet() повертає список усіх пар у нашій HashMap (оскільки наша мапа складається якраз із таких пар-Entry, то ми перебираємо саме пари, а не окремо ключі або значення). Виведення:
212133=Лідія Аркадіївна Бублікова 8082771=Дональд Джон Трамп 162348=Іван Михайлович Серебряков
Не забудь вивчити офіційну документацію Oracle щодо HashMap.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ