Eine Aufgabe, mehrere Werkzeuge
Du hast einige Daten gesammelt - in einer ArrayList, einem HashSet, einer HashMap - und möchtest nun jedes Element besuchen. Java bietet dir mehrere Wege dafür, und welchen du wählst, hängt davon ab, ob du den Index brauchst, ob du mitten in der Schleife Elemente entfernen musst und ob du eine methoden- oder schleifenartige Syntax bevorzugst.
Die gute Nachricht: Jede Collection (Liste, Menge, Queue) unterstützt dieselbe erweiterte for-Schleife, also kannst du sie alle auf dieselbe Weise durchlaufen, sobald du sie gelernt hast.
Lies den Doppelpunkt als „in": „für jedes lang in langs". Du fasst nie einen Index an, also kann nichts schiefgehen.
Die for-each-Schleife
Das erweiterte for ist die Standardwahl für das schlichte „tu etwas mit jedem Element". Es liest sich klar und funktioniert über alle Collection-Typen hinweg gleich - hier ein HashSet, das überhaupt keine Indizes hat:
Eines solltest du dir über ein HashSet merken: Es hat keine definierte Reihenfolge, also können die Elemente in beliebiger Reihenfolge ausgegeben werden. Das for-each besucht sie dennoch jeweils genau einmal.
Wenn du den Index brauchst
Das for-each gibt dir das Element, aber nicht seine Position. Wenn du den Index wirklich brauchst - um Zeilen zu nummerieren oder benachbarte Elemente zu betrachten - verwende eine gezählte Schleife mit size() und get(i). Das funktioniert mit einer List, die positionsbasiert ist; Mengen und Maps haben keinen Index, dieser Stil ist also nicht auf sie anwendbar.
Greife nicht aus reiner Gewohnheit dazu. Wenn du i für nichts anderes als get(i) verwendest, ist die for-each-Variante kürzer und schwerer falsch zu machen.
Eine Map durchlaufen
Eine Map ist keine Collection, daher kannst du nicht direkt mit for-each über sie iterieren. Stattdessen durchläufst du eine ihrer drei Ansichten. Die häufigste ist entrySet(), die dir jedes Schlüssel-Wert-Paar zusammen liefert:
Wenn du nur die Schlüssel brauchst, durchlaufe ages.keySet(); wenn du nur die Werte brauchst, durchlaufe ages.values(). Bevorzuge entrySet(), wenn du beides brauchst - die Schlüssel zu durchlaufen und dann innen ages.get(key) aufzurufen, führt ohne Grund bei jeder Iteration eine zweite Suche durch.
Der Iterator
Das for-each ist eigentlich syntaktischer Zucker über einem Iterator - einem Objekt, das eine Collection über hasNext() und next() Element für Element durchläuft. Diese Schleife schreibst du selten von Hand, mit einer wichtigen Ausnahme: Es ist der sichere Weg, Elemente während der Iteration zu entfernen.
it.remove() löscht das Element, das next() zuletzt zurückgegeben hat, und der Iterator bleibt gültig. Dies ist der einzige zulässige Weg, eine Collection während einer manuellen Schleife zu verändern.
Die Falle: ConcurrentModificationException
Wenn du add oder remove auf der Collection selbst innerhalb einer for-each-Schleife aufrufst, bekommst du eine ConcurrentModificationException - der Iterator bemerkt, dass sich die Collection unter ihm geändert hat, und weigert sich fortzufahren. Das ist einer der häufigsten Anfängerfehler.
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4));
for (int n : nums) {
if (n % 2 == 0) {
nums.remove(Integer.valueOf(n)); // throws ConcurrentModificationException
}
}
Die Lösung ist fast immer removeIf, das die Absicht in einer Zeile ausdrückt und die Iteration für dich übernimmt:
removeIf funktioniert mit jeder Collection, derselbe Aufruf räumt also auch ein HashSet auf.
Die forEach-Methode
Jede Collection hat außerdem eine forEach-Methode, die ein Lambda entgegennimmt und es auf jedes Element anwendet. Sie ist eine funktionalere, ausdrucksorientierte Alternative zur Schleife - praktisch für kurze Einzeiler:
Beachte, dass Map.forEach direkt ein Lambda mit zwei Argumenten (key, value) entgegennimmt - kein entrySet() nötig. Verwende forEach für schnelle Seiteneffekte; greife auf die reguläre for-Schleife zurück, wenn der Rumpf wächst oder du break brauchst, um vorzeitig auszusteigen, was ein Lambda nicht kann.
Weiter: Methoden
Du hast nun Daten in Collections verpackt und sie auf jede Weise durchlaufen, die Java bietet. Der nächste Schritt ist, Verhalten zu verpacken: eigene Methoden zu schreiben, sodass du einen Logikblock benennen, ihm Eingaben übergeben und ihn wiederverwenden kannst - das ist die nächste Seite.
Häufig gestellte Fragen
Wie durchläuft man eine Liste in Java?
Am saubersten ist die erweiterte for-Schleife (for-each): for (String s : list) { ... }. Sie funktioniert mit jeder Collection - ArrayList, HashSet und so weiter. Verwende eine indexbasierte Schleife mit get(i) nur dann, wenn du die Position wirklich brauchst, und einen Iterator, wenn du während der Schleife Elemente entfernen musst.
Wie iteriert man in Java über eine HashMap?
Eine Map ist keine Collection, also durchläufst du eine ihrer Ansichten. Die übliche Wahl ist for (Map.Entry<K, V> e : map.entrySet()), was dir den Schlüssel (e.getKey()) und den Wert (e.getValue()) in einem Durchgang gibt. Du kannst auch map.keySet() für die Schlüssel oder map.values() für die Werte durchlaufen.
Warum bekomme ich beim Schleifen eine ConcurrentModificationException?
Du hast add oder remove auf der Collection aufgerufen, während eine for-each-Schleife sie durchlief. Die for-each-Schleife verwendet im Hintergrund einen Iterator, und dieser erkennt, dass sich die Collection strukturell geändert hat. Behebe das, indem du über die eigene remove()-Methode des Iterator entfernst oder removeIf(...) aufrufst, statt zu schleifen.