.

Das Map Interface dient als Vorlage für Objekte die Schlüsseln Werte zuordnen. Oder man kann auch sagen eine Collection von Schlüssel-Wert-Paaren. Die Map ist nicht wirklich eine Collection da das Map Interface nicht das Collection Interface implementiert. Die Maps stellen einen unabhängigen Zweig im Java Collections Framework dar.

Übersicht der Collection API

Übersicht – Map Interface

Da das Map Interface nicht wirklich eine Collection ist, sind seine Eigenschaften und das Verhalten auch etwas anders als die der anderen Collections wie zum Beispiel List oder Set. Eine Map kann keine doppelten Schlüsseleinträge haben und ein Schlüssel ist in der Regel nur einem Wert zugeordnet. Manche Implementierungen der Map erlauben aber ’null‘ Schlüssel und ’null‘ Werte. Dazu gehören die HashMap und die LinkedHashMap, die TreeMap hingegen erlaubt dies nicht.
Die Ordnung einer Map ist ebenfalls von den verschiedenen Implementierungen Abhängig, die TreeMap und die LinkedHashMap haben eine definierte Ordnung. die HashMap allerdings nicht.

Einsatz einer Map

Maps eignen sich sehr gut für die Zuordnung von Schlüssel-Wert-Verknüpfungen. Ein Beispiel dafür wäre ein Wörterbuch. Eine Map findet somit immer dann ihr geeignetes Einsatzgebiet wenn Elemente nach Schlüsseln abgerufen und aktualisiert oder nach Schlüsseln gesucht werden soll. Einige Beispiele:

  • Fehler Codes und die zugehörigen Beschreibungen
  • Postleitzahlen und Ortsnamen.
  • Manager und Angestellte – Dabei wird jedem Manager eine Liste von Angestellten zugeordnet für die dieser zuständig ist.
  • Studiengang und Studenten – Dem Studiengang wird eine Liste von Studenten zugeordnet.

Map Interface Implementierungen

Im Vererbungsbaum des Map Interface gibt es mehrere Implementierungen, jedoch nur drei Hauptimplementierungen: HashMap, LinkedHashMap und TreeMap. Von diesen Implementierungen sollen hier die Eigenschaften und ihr Verhalten genauer betrachtet werden.

  • HashMap<K, V> – Diese Implementierung verwendet eine Hashtable als Basis für die Datenstruktur. Sie implementiert alle Map-Operationen und erlaubt ’null‘ Werte und einen ’null‘ Schlüssel. Diese Klasse entspricht in etwa der Hashtable, sie ist aber nicht synchronisiert. Die HashMap garantiert nicht die Reihenfolge (Ordnung) ihrer Schlüssel-Werte-Elemente. Daher sollte eine HashMap nur eingesetzt werden, wenn die Reihenfolge keine Rolle spielt und ’null‘ Einträge akzeptabel sind.
  • LinkedHashMap<K, V> – Diese Implementierung verwendet ebenfalls eine Hashtable und zusätzlich eine LinkedList als zugrundeliegende Datenstrukturen, daher ist die Reihenfolge einer LinkedHashMap vorhersehbar, wobei die Einfügereihenfolge die Standardreihenfolge ist. Diese Implementierung erlaubt auch ’null‘ wie HashMap. Die Verwendung einer LinkedHashMap bietet sich an, wenn eine Map nötig ist, deren Schlüssel-Wert-Paare nach ihrer eingefügten Reihenfolge sortiert sind.
  • TreeMap<K, V> – Diese Implementierung verwendet einen rot-schwarzen Baum als zugrunde liegende Datenstruktur. Eine TreeMap wird nach der natürlichen Reihenfolge ihrer Schlüssel sortiert oder durch einen Comparator, der bei der Erstellung angegeben wird. Diese Implementierung lässt keine ’null‘ Werte oder Schlüssel zu. Eine TreeMap sollte verwendet werden, wenn erwünscht ist, dass eine Map ihre Schlüssel-Wert-Paare nach der natürlichen Reihenfolge der Schlüssel (z. B. alphabetische oder numerische Reihenfolge) oder nach einer angegebenen Reihenfolge, mittels Comperator, sortiert wird.

In weiteren Verlauf werden wir sehen wie eine Map bei der Täglichen Arbeit eingesetzt wird.

Eine neue Map erstellen

HashMap

Wie bei anderen Typen der Collections auch sollte immer das Interface Map, generics und der Diamond-Operator zum erstellen einer HashMap verwendet werden.

Map<Integer, String> mapPLZOrt = new HashMap<>();
mapPLZOrt.put(52070, "Aachen");
mapPLZOrt.put(10245, "Berlin");
mapPLZOrt.put(20359, "Hamburg");
System.out.println(mapPLZOrt);

Diese Map verknüpft Postleitzahlen mit den passenen Ortsnamen.

{10245=Berlin, 52070=Aachen, 20359=Hamburg}

Wie man an der Ausgabe sieht gibt es in der HashMap keine definierte Ordnung.

Eine neue Map aus einer zu erstellen die bereits existiert ist auch sehr simpel.

Map<Integer, String> mapPLZ = new HashMap<>(mapPLZOrt);

Beim erstellen der neuen ‚mapPLZ‘ werden die Elemente der ‚mapPLZOrt‘ kopiert. Im nachhinein besteht keine Verbindung zwischen den beiden Map Objekten.

LinkedHashMap

Das folgende Beispiel zeigt das erstellen einer Map in der einer Kundennummer ein Namen zugeortnet wird.

Map<Integer, String> kunden = new LinkedHashMap<>();
kunden.put(1000756, "Franz Hermann");
kunden.put(1000757, "Henry Bold");
kunden.put(1000555, "Josefine Fein");	System.out.println(kunden);

An der Ausgabe sehen wir dass die Elemente in der Reihenfolge des Einfügens geordnet sind.

{1000756=Franz Hermann, 1000757=Henry Bold, 1000555=Josefine Fein}

TreeMap

Die TreeMap wird im folgenden Besipiel für die verknüpfung von Längen mit entsprechenden Größenbezeichnungen genutzt.

Map<Integer, String> sizes = new TreeMap<>();
sizes.put(20, "S");
sizes.put(40, "X");
sizes.put(30, "L");
System.out.println(groessen);

Hier sehen wir das die Ordnung durch die numerischen Längenangaben entsteht.

{20=S, 30=L, 40=X}

Grundlegende Methoden des Map Interface

Die Grundoperationen einer Map sind Zuordnung: ‚put()‘, Nachschlagen: get(), Überprüfen ob ein Schlüssel oder Wert enthalten ist: ‚containsKey()‘ oder ‚containsValue()‘, Änderung wie Entfernen oder Ersetzen: ‚remove()‘ oder ‚replace()‘ und Kardinalität ‚size()‘ und ‚isEmpty()‘.

Einem Wert einen Schlüssel zuordnen

Die Methode put(K, V) ordnet den angegebenen Wert V dem angegebenen Schlüssel K zu. Wenn die Zuordnung bereits eine Zuordnung für den Schlüssel enthält, wird der alte Wert durch den angegebenen Wert ersetzt.

Map<Integer, String> mapPLZOrt = new HashMap<>();
mapPLZOrt.put(52070, "Düren");
mapPLZOrt.put(10245, "Berlin");
mapPLZOrt.put(20359, "Hamburg");
System.out.println(mapPLZOrt);
mapPLZOrt.put(52070, "Aachen");
System.out.println(mapPLZOrt);

Ausgabe:

{10245=Berlin, 52070=Düren, 20359=Hamburg}
{10245=Berlin, 52070=Aachen, 20359=Hamburg}

Ein Wert mittels Schlüssel abrufen

Die Methode get(K) gibt den dem angegebenen Schlüssel zugeordneten Wert zurück oder gibt null zurück, wenn die Map keine Zuordnung für den Schlüssel enthält.

String ort1 = mapPLZOrt.get(52070);
String ort2 = mapPLZOrt.get(52066);
System.out.println(ort1); 
System.out.println(ort2);

Ausgabe:

Aachen
null

Überprüfen ob ein Schlüssel enthalten

Die Methode containsKey(K) gibt ‚true‘ zurück, wenn die Map eine Zuordnung für den übergebenen Schlüssel enthält.

if(mapPLZOrt.containsKey(52070)){
  System.out.println("Der gesuchte Schlüssel ist vorhanden!");
}

Überprüfen ob ein Wert enthalten

Die Methode containsValue(V) gibt ‚true‘ zurück, wenn die Map einen oder mehrere Schlüssel enthält, die dem angegebenen Wert zugeordnet sind.

if(mapPLZOrt.containsValue("Berlin")){
  System.out.println("Es gibt den gesuchten Wert!");
}

Ein Wert-Schlüssel-Paar entfernen

Die Methode remove(K) entfernt einen Schlüssel und dessen zugeordneten Wert aus der Map, falls vorhanden (wir kümmern uns nur um den Schlüssel, und der Wert spielt keine Rolle). Diese Methode gibt den Wert zurück, der in der Map zuvor dem Schlüssel zugeordnet war, oder null, wenn die Map keine Zuordnung für den Schlüssel enthält.

if(mapPLZOrt.remove(52070) != null) {
  System.out.println("Key-Value-Paar gelöscht!");
}

In ähnlicher Weise entfernt die Methode remove(K, V) die Zuordnung des angegebenen Schlüssels und den Wert. Und gibt ‚true‘ zurück, wenn das Schlüssel-Wert-Paar entfernt wurde. Passt allerdings der Schlüssel nicht zum Wert bekommt man ‚false‘ zurück und es wird nichts gelöscht.

if(mapPLZOrt.remove(52070, "Aachen")) {
  System.out.println("52070 Aachen entfernt");
}
if(mapPLZOrt.remove(20349, "Aachen")) {
  System.out.println("2349 Aachen entfernt");
} else {
 System.out.println("Das K,V Paar 2349 Aachen gibt es nicht");
}

Ausgabe:

52070 Aachen entfernt
Das K,V Paar 2349 Aachen gibt es nicht

Ersetzen eines Werts, der einem Schlüssel zugeordnet ist

Die Methode replace(K, V) ersetzt den Eintrag für den angegebenen Schlüssel nur, wenn er derzeit einem bestimmten Wert zugeordnet ist. Diese Methode gibt den vorherigen Wert zurück, der dem angegebenen Schlüssel zugeordnet war.

String ortAlt = mapPLZOrt.replace(20349, "Hamburg St Pauli");
System.out.println("Alter Wert: " + ortAlt);
System.out.println("Neuer Wert: " + mapPLZOrt.get(20349));

Ausgabe:

Alter Wert: Hamburg
Neuer Wert: Hamburg St Pauli

In ähnlicher Weise ersetzt die Methode replace(K, V old, V new) den Eintrag für den angegebenen Schlüssel nur, wenn er derzeit dem angegebenen Wert zugeordnet ist. Diese Methode gibt ‚true‘ zurück, wenn der Wert ersetzt wurde.

Anzahl der Elemente in einer Map

Die size() Methode gibt die Anzahl der Schlüssel-Wert-Paare in einer Map zurück.

int size = mapPLZOrt.size();
System.out.printf("Die Map enthält %d Elemente", size);

Ausgabe:

Die Map enthält 3 Elemente

Testen ob eine Map leer ist

Die isEmpty() Methode gibt ‚true‘ zurück, wenn die Map keine Schlüssel-Wert-Paare enthält.

if(mapPLZOrt.isEmpty()) {
  System.out.println("Die Map ist nicht leer!");
}

Ausgabe:

Die Map ist nicht leer!

Über eine Map iterieren mit Collection views

Da eine Map keine echte Collection ist, gibt es keine direkte Methode zum itterieren über eine Map. Stattdessen kann man über eine Map mithilfe ihrer Collection views iterieren. Die Implementierung einer Map muss die folgenden drei Methoden der Collection views bereitstellen.

  • keySet() – Gibt eine Set der in der Map enthaltenen Schlüssel zurück.
  • values() – Gibt eine Collection mit den Werten zurück, die in der Map enthalten sind.
  • entrySet() – Gibt eine Set der in dieser Map enthaltenen Schlüssel-Wert-Paare zurück.

Die folgenden Code Beispielen gegen immer von der Map ’sizes‘ aus.

Map<Integer, String> sizes = new TreeMap<>();
sizes.put(20, "S");
sizes.put(40, "X");
sizes.put(30, "L");

Iterieren über Schlüssel

Set<Integer> sizeSet = sizes.keySet();
Iterator<Integer> iterator = sizeSet.iterator();
while (iterator.hasNext()) {
  Integer cm = iterator.next();
  String size = sizes.get(cm); 
  System.out.println(cm + "cm entsprechen Größe: " + size);
}

Ausgabe:

20cm entsprechen Größe: S
30cm entsprechen Größe: L
40cm entsprechen Größe: X

Iterieren über Werte

Collection<String> sizeNames = sizes.values();
System.out.print("Vorhandene Größen: ");
for (String s : sizeNames) {
  System.out.print(s + ", ");
}

Ausgabe:

Vorhandene Größen: S, L, X, 

Iterieren über Schlüssel-Wert-Paare

Set<Map.Entry<Integer, String>> mapContent = sizes.entrySet();
Iterator<Map.Entry<Integer, String>> it = mapContent.iterator();
while(it.hasNext()) {
  Map.Entry<Integer, String> e = it.next();
  System.out.println(e.getValue()+" = "+e.getKey()   + "cm");
}

Ausgabe:

S = 20cm
L = 30cm
X = 40cm

Ausführen von Bulk Operations auf zwei Maps

Es gibt zwei Massenoperationen die auf Maps angewendet werden können, das sind clear() und putAll().

Entleren einer Map

Nach dem Aufruf von clear() auf eine Map ist diese leer.

mapPLZOrt.clear();
System.out.println("Is map empty? " + mapPLZOrt.isEmpty());

Ausgabe:

Is map empty? true

Einer Map eine andere hinzufügen

Die Methode putAll(Map<K, V>) kopiert alle Zuordnungen von der angegebenen Map in diein die aufrufende Map.

Map<String, Integer> ages = new HashMap<>(); 
ages.put("Carlson", 8);
ages.put("Madita", 5);
ages.put("Michel", 7); 
Map<String, Integer> ages2 = new HashMap<>(); 
ages2.put("Carlson", 8);
ages2.put("Anton", 12);
ages2.put("Annika", 11); 
System.out.println("Vorher: " + ages); 
ages.putAll(ages2); 
System.out.println("Nachher: " + ages);

Ausgabe:

Vorher: {Michel=7, Carlson=8, Madita=5}
Nachher: {Anton=12, Annika=11, Michel=7, Carlson=8, Madita=5}

Parallele Verarbeitung von Maps

Im Gegensatz zur synchronisierten Hashtable werden HashMap, TreeMap und LinkedHashMap nicht synchronisiert. Wenn threadsichheit nötig ist, sollten die ConcurrentHashMap anstelle der HashMap verwendet werden. Oder man nutzt die Collections.synchronizedMap(myMap) Methode. Diese gibt eine threadsichere Map zurück, die von der übergebenen Map unterstützt wird.

Map<Integer, String> myMap = new HashMap<>();
Map<Integer, String> sMap = Collections.synchronizedMap(myMap);

Wie bei den anderen Collections auch muss eine threadsicher Map manuell syncronisiert werden, bevor man darüber iterieren kann.

Set<Integer> keySet = sMap.keySet(); 
synchronized (sMap) {
  Iterator<Integer> iterator = keySet.iterator(); 
  while (iterator.hasNext()) {
    Integer key = iterator.next();
    String value = map.get(key);
  }
}

Wenn man eine SortedMap verwenden, z.B. eine TreeMap sollte die unbedingt die Methode Collections.synchronizedSortedMap(mapTree) verwendet werden.

HINWEIS: Wenn man eigene Typ für den Schlüssel und/oder den Wert verwendet (z. B. Student oder Arbeitnehmer), müssen diese Klasse die Methoden equals() und hashCode() ordnungsgemäß implementieren. Denn diese zwei Methoden sind die grundlage für die zuverlässige funktionsweise einer Map.

Basierent auf dem Artikel Java Map Collection Tutorial and Examples von Nam Ha Minh.


0 Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.