1. Wprowadzenie
W prawdziwym życiu często spotykamy się z sytuacjami, gdy każdemu unikalnemu „kluczowi” trzeba przypisać jakąś „wartość”. Książka telefoniczna przechowuje numer telefonu i imię osoby, słownik wiąże słowo z jego tłumaczeniem, a w tabeli ocen każdy student ma swoje imię i odpowiedni wynik.
W Javie do takich zadań służy interfejs Map. To kolekcja, która przechowuje pary „klucz — wartość” (key-value pair).
Najważniejsze właściwości Map:
- Każdy klucz jest unikalny (duplikaty nie są dozwolone).
- Jednemu kluczowi może odpowiadać tylko jedna wartość.
- Wartości mogą się powtarzać.
Wyobraźmy sobie analogię. Jeśli lista (List) to jak kolejka w stołówce (każdy stoi na swojej pozycji, można odwołać się po numerze), to mapa (Map) to jak szafka z przegródkami: każda przegródka ma numer (klucz), a w środku leży coś własnego (wartość).
Interfejs Map: podstawowe operacje
Interfejs Map deklaruje podstawowe metody do pracy z parami klucz–wartość:
| Metoda | Opis |
|---|---|
|
Dodać/zastąpić wartość dla klucza |
|
Pobrać wartość dla klucza |
|
Usunąć parę według klucza |
|
Sprawdzić, czy istnieje taki klucz |
|
Sprawdzić, czy istnieje taka wartość |
|
Liczba par w mapie |
|
Sprawdzić, czy Map jest pusta |
|
Usunąć wszystkie pary |
Typy K i V — to parametry generyczne: K (Key) — typ klucza, V (Value) — typ wartości.
3. Klasa HashMap: szybki dostęp po kluczu
Co to jest HashMap?
HashMap to najpopularniejsza implementacja interfejsu Map. Zapewnia szybki dostęp do wartości po kluczu.
Ważne: HashMap nie gwarantuje porządku przechowywania elementów! Jeśli dodasz klucze w określonej kolejności, przy iteracji mogą występować w innej.
Jak utworzyć HashMap?
import java.util.HashMap;
import java.util.Map;
public class Example {
public static void main(String[] args) {
// Tworzymy mapę: klucz — String, wartość — Integer
Map<String, Integer> ages = new HashMap<>();
// Dodajemy elementy
ages.put("John", 25);
ages.put("Peter", 30);
ages.put("Mary", 22);
// Pobieramy wartość dla klucza
int vasyaAge = ages.get("John");
System.out.println("Wiek John: " + vasyaAge); // 25
// Sprawdzamy obecność klucza
if (ages.containsKey("Mary")) {
System.out.println("Mary jest na liście!");
}
// Usuwamy element
ages.remove("Peter");
// Iterujemy po wszystkich parach klucz-wartość
for (String name : ages.keySet()) {
System.out.println(name + ": " + ages.get(name));
}
}
}
Wynik:
Wiek John: 25
Mary jest na liście!
John: 25
Mary: 22
Cechy HashMap
Najważniejsze, o czym należy pamiętać: klucze w HashMap zawsze są unikalne. Jeśli wstawisz nowy element z już istniejącym kluczem, stara wartość zostanie zastąpiona nową.
Wartości mogą się powtarzać: kilka różnych kluczy może wskazywać tę samą wartość.
Jeszcze jedna ważna kwestia — porządek elementów. HashMap nie dba o kolejność dodania. Przy wypisywaniu wpisy mogą być przemieszane — to normalne zachowanie.
4. Klasa TreeMap: sortowanie według klucza
W przeciwieństwie do HashMap, klasa TreeMap przechowuje elementy w porządku posortowanym według klucza.
Kiedy używać TreeMap?
Gdy ważne jest, aby elementy były ułożone rosnąco (lub malejąco) według kluczy. Na przykład, jeśli chcesz wypisać książkę telefoniczną alfabetycznie.
Przykład:
import java.util.Map;
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
Map<String, String> phoneBook = new TreeMap<>();
phoneBook.put("John", "+1-900-123-45-67");
phoneBook.put("Mary", "+1-900-555-55-55");
phoneBook.put("Peter", "+1-900-222-33-44");
for (String name : phoneBook.keySet()) {
System.out.println(name + ": " + phoneBook.get(name));
}
}
}
Wynik:
Mary: +1-900-555-55-55
Peter: +1-900-222-33-44
John: +1-900-123-45-67
Zwróć uwagę: klucze są posortowane alfabetycznie.
5. Podstawowe operacje na Map
Dodawanie i zastępowanie elementów
Map<String, Integer> scores = new HashMap<>();
scores.put("Anna", 90);
scores.put("Ivan", 85);
scores.put("Anna", 95); // Nadpisze wartość dla "Anna"
Pobieranie wartości
Integer annaScore = scores.get("Anna"); // 95
Integer unknown = scores.get("John"); // null, jeśli takiego klucza nie ma
Sprawdzanie obecności klucza lub wartości
scores.containsKey("Ivan"); // true
scores.containsValue(85); // true
Usuwanie pary według klucza
scores.remove("Ivan");
Rozmiar mapy i czyszczenie
int size = scores.size();
scores.clear(); // Usuwa wszystkie elementy
5. Iteracja po elementach Map
Map to nie lista — nie ma tu indeksów. Można jednak iterować:
Po kluczach:
for (String key : scores.keySet()) {
System.out.println("Klucz: " + key + ", Wartość: " + scores.get(key));
}
Po wartościach:
for (Integer value : scores.values()) {
System.out.println("Wartość: " + value);
}
Po parach klucz–wartość (najlepszy sposób):
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " => " + value);
}
Kiedy używać HashMap, a kiedy — TreeMap?
HashMap to uniwersalny, domyślny wybór. Jeśli kolejność kluczy nie ma znaczenia, a najważniejsza jest szybkość operacji, prawie zawsze wybiera się właśnie ją.
TreeMap przydaje się, gdy potrzebny jest porządek. Automatycznie przechowuje klucze posortowane i pozwala szybko znaleźć minimalny/maksymalny klucz lub pracować z zakresami.
Podsumowując: w 90 % przypadków wybieramy HashMap. Gdy dane mają być od razu „w porządku”, używamy TreeMap.
6. Przykłady użycia Map
Przykład 1: Książka telefoniczna
Map<String, String> phoneBook = new HashMap<>();
phoneBook.put("Kate", "+1-999-111-22-33");
phoneBook.put("Oliver", "+1-999-222-33-44");
phoneBook.put("Kate", "+1-999-555-66-77"); // Stary numer Katyi zostanie zastąpiony nowym
for (Map.Entry<String, String> entry : phoneBook.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
Wynik:
Oliver: +1-999-222-33-44
Kate: +1-999-555-66-77
Przykład 2: Zliczanie wystąpień słów
Załóżmy, że mamy listę słów i chcemy sprawdzić, ile razy każde słowo występuje:
import java.util.*;
public class WordCount {
public static void main(String[] args) {
List<String> words = Arrays.asList("jabłko", "banan", "jabłko", "gruszka", "banan", "jabłko");
Map<String, Integer> counts = new HashMap<>();
for (String word : words) {
int oldCount = counts.getOrDefault(word, 0); // jeśli klucza nie ma — 0
counts.put(word, oldCount + 1);
}
System.out.println(counts); // {gruszka=1, jabłko=3, banan=2}
}
}
7. Typowe błędy podczas pracy z Map
Błąd nr 1: Mylenie kluczy i wartości. Początkujący często próbują pobrać wartość po indeksie, jak na liście, albo zapominają, że klucze muszą być unikalne. W Map nie ma indeksów — są tylko klucze.
Błąd nr 2: Używanie kluczy i wartości null. W HashMap dopuszczalny jest klucz null, ale w TreeMap — nie (pojawi się NullPointerException). Wartości mogą być null w obu implementacjach, lecz rzadko jest to użyteczne.
Błąd nr 3: Oczekiwanie porządku elementów w HashMap. HashMap nie gwarantuje żadnej kolejności. Jeśli potrzebny jest porządek — użyj LinkedHashMap (zachowuje kolejność dodawania) lub TreeMap (sortuje według klucza).
Błąd nr 4: Modyfikacja Map podczas iteracji. Jeśli w pętli iterujesz po Map i jednocześnie dodajesz/usuwasz elementy — może wystąpić ConcurrentModificationException. Do takich zadań użyj iteratora z metodą remove() lub specjalnych kolekcji.
Błąd nr 5: Porównywanie kluczy i wartości za pomocą == zamiast equals. Map używa metody equals do porównywania kluczy (i wartości). Jeśli tworzysz własne klasy‑klucze, koniecznie nadpisz equals i hashCode.
GO TO FULL VERSION