Maps
Map und HashMap
- Maps dienen dem Erstellen von Wörterbüchern (dictionaries)
- Einem Schlüssel (key) wird ein Wert (value) zugeordnet
- Die gängigste Implementierung des
Map
-Interfaces istHashMap
, die sich auf dem Hashwerten (hashCode()
) der Objekte abstützt - Bei gut verteilten Hashwerten ist das Lesen eines Objektes aus einer HashMap O(1)
Während die Aufgabe der Collections darin besteht, Mengen von Objekte zu speichern, geht es bei einer Map
um den Aufbau einer Datenstruktur aus Schlüssel-Wert-Paaren (key/value pairs). Hierbei werden Objekte als Schlüssel für andere Objekte verwendet. Der klassische Anwendungsfall hierfür ist ein Cache, z. B. im Browser: der Schlüssel ist die URL (String
), der Wert der Inhalt der zwischengespeicherten Webseite. Andere Namen für eine Map sind assoziatives Array oder einfach nur Hash.
Da die am häufigsten verwendete Implementierung des Map
-Interfaces auf Hash-Tabellen basiert, kann der Zugriff auf ein Objekt in konstanter Zeit erfolgen, d. h. die Menge der gespeicherten Objekte hat keine Auswirkung auf die Zeit, die das Finden und Auslesen braucht.
Viele Programmiersprachen kennen das Konzept der Map, viele sogar als eingebautes Sprachmittel. So profitieren JavaScript, PHP und Ruby stark von den assoziativen Arrays.
Methoden von Map<K, V>
int size()
– Anzahl der Elementeboolean isEmpty()
– (size() == 0
)boolean containsKey(Object key)
– Testet, ob Schlüsselkey
vorhanden istboolean containsValue(Object value)
– Testet, ob Wertvalue
vorhanden istV get(Object key)
– Liest den Wert zum Schlüsselkey
V put(K key, V value)
– Setzt Schlüsselkey
und Wertvalue
V remove(Object key)
– Entfernt den Wert zukey
(undkey
selbst)
void putAll(Map<? extends K, ? extends V> m)
– Fügt eine ganze Map hinzuvoid clear()
– Löscht die MapSet<K> keySet()
– Alle Schlüssel als SetCollection<V> values()
– Alle Werte als CollectionSet<Map.Entry<K, V>> entrySet()
– Alle Einträge als spezielles Set
Viele Methoden aus dem Interface Map
sind denen aus den Collections nicht unähnlich nur, dass hier entweder nach Schlüssel und Wert getrennt wird oder ein Schlüssel übergeben werden muss, um auf das Objekt zuzugreifen.
Man kann aus der Map über die Methoden values
und keySet
die Menge aller Werte bzw. aller Schlüssel auslesen. Üblicherweise verwendet man allerdings zum Iterieren über alle Elemente der Map die spezielle Methode entrySet
, die in einem der folgenden Beispiele noch dargestellt wird.
Beispiel: Map und HashMap
Map<String, String> map = new HashMap<>();
map.put("go", "gehen");
map.put("walk", "gehen");
map.put("sleep", "schlafen");
map.put("learn", "lernen");
System.out.println(map); // --> {learn=lernen, sleep=schlafen,
// walk=gehen, go=gehen}
System.out.println(map.get("go")); // --> gehen
System.out.println(map.get("walk")); // --> gehen
System.out.println(map.get("learn")); // --> lernen
System.out.println(map.keySet()); // --> [learn, sleep, walk, go]
System.out.println(map.values()); // --> [lernen, schlafen, gehen, gehen]
Das Beispiel zeigt die elementaren Operationen auf einer Map:
- Einfügen von Schlüssel/Wert-Paaren mit
put()
- Auslesen von Elementen über den Schlüssel mit
get()
- Auslesen aller Schlüssel mit
keySet()
- Auslesen aller Werte mit
values()
Zusätzlich sieht man, dass die toString()
-Methode so überschrieben ist, dass der Inhalt der Map im Format key=value
ausgegeben wird.
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
String key = entry.getKey();
String value = entry.getValue();
System.out.printf("%s engl. fuer %s%n", key, value);
}
Das Iterieren über den gesamten Inhalt einer Map erfolgt üblicherweise wie in diesem Beispiel dargestellt. Man kann über die Methode entrySet()
ein Set
erhalten, das aus speziellen Objekten vom Typ Map.Entry
besteht. Jedes dieser Objekte enthält einen Schlüssel und den dazugehörigen Wert. Den Schlüssel kann man über getKey()
, den Wert über getValue()
aus dem Map.Entry
-Objekt auslesen. Da Map
ein generischer Typ mit den Parametern K
für den Typ des Schlüssels und V
für den Typ des Wertes ist, hat Map.Entry
ebenfalls diese Typ-Parameter.
Beispiel: Komplexer Schlüssel
Map<Personalnummer, Mitarbeiter> map = new HashMap<>();
Mitarbeiter meier = new Mitarbeiter("Meier", 1200.00);
Mitarbeiter mueller = new Mitarbeiter("Mueller", 2400.00);
Mitarbeiter schulze = new Mitarbeiter("Schulze", 1200.00);
map.put(new Personalnummer("4711"), meier);
map.put(new Personalnummer("4242"), mueller);
map.put(new Personalnummer("0007"), schulze);
System.out.println(map.containsKey(new Personalnummer("0007"))); // --> true
System.out.println(map.containsValue(meier)); // --> true
Obwohl sehr häufig String
als Schlüssel für eine Map verwendet wird, kann jedes Objekt, das korrekt equals()
und hashCode()
überschreibt, als Schlüssel verwendet werden. Diese Umstand demonstriert dieses Beispiel bei dem als Schlüssel eine Klasse Personalnummer
verwendet wird.
Beispiel: Gleiche Schlüssel
Map<String, String> map = new HashMap<>();
map.put("gehen", "go");
map.put("gehen", "walk");
map.put("schlafen", "sleep");
map.put("lernen", "learn");
System.out.println(map);
{gehen=walk, lernen=learn, schlafen=sleep}
Verwendet man in einer Map denselben Schlüssel mehrfach (hier "gehen"
) so ersetzt der zweite Wert den ersten, d. h. im Beispiel wird nur "walk"
aber nicht "go"
gespeichert.
Aufgrund dieses Verhaltens ersetzt man bei einer Map einen Wert einfach, indem man einen anderen Wert mit demselben Schlüssel einfügt. Ein vorheriges Löschen ist nicht notwendig.
Vorsicht: Veränderbare Objekte als Key
- Das Verhalten von
Map
ist undefiniert, wenn der Schlüssel nach dem Hinzufügen verändert wird - Schlimmstenfalls werden die Objekte nie mehr wiedergefunden
- Daher sollte man keine veränderbaren Objekte (mutuable objects) als Schlüssel verwenden
- Eine Map darf sich selbst nicht als Schlüssel enthalten
- Eine Map sollte sich selbst nicht als Wert enthalten
Da das Interface Map
(analog zu Set
) in den allermeisten Fällen durch eine Hash-Tabelle realisiert wird, darf man auf keinen Fall den Inhalt des Objektes, das als Schlüssel verwendet wurde, verändern, solange es in der Map gespeichert ist. Die Begründung für diese Regel wurde bereits beim Set
gegeben und soll hier nicht wiederholt werden. Den Inhalt des Wertes darf man aber beliebig ändern, ohne dass dies Auswirkungen hätte.