Iterable und Iterator

Iterable und Iterator

  • Iterable-Instanzen liefern einen Iterator zurück
  • Iterator dient dem Durchlaufen von Collections (iterieren)
  • Iterable und Iterator sind die Grundlage für die foreach-Schleife
  • Methoden auf Iterator<T>
    • boolean hasNext() - Gibt an, ob noch Elemente vorhanden sind
    • T next() - Geht zum nächsten Element und gibt es zurück
    • remove() - Entfernt das aktuelle Element (muss nicht unterstützt werden)
  • Iterator kann nur vorwärts laufen, für bestimmte Collections gibt es den ListIterator, der rückwärts navigieren kann

Immer wenn man mit Sammlungen von Objekten hantiert ist das Durchlaufen der Sammlung (iterieren) eine der grundlegenden Operationen. Man möchte sukzessive die Objekte aus der Sammlung anschauen. Hierzu dienen die Interfaces Iterable und Iterator. Etwas verwirrend mag erscheinen, dass man die Collection offensichtlich nur vorwärts durchlaufen kann und nicht wahlfrei auf einzelne Elemente zugreifen kann – man kann nicht sagen „gibt mir das 5. Element“. Der Grund wird später zwar noch genauer beleuchtet, hier sei aber schon einmal angemerkt, dass es Collections gibt (Set), in denen die Objekte ohne jede Ordnung herumliegen und damit gar kein 5. Element existiert.

Wenn der Iterator bereits beim letzten Element der Collection angekommen ist, wirft next() eine Ausnahme vom Typ NoSuchElementException.

remove() muss von Collections nicht unterstützt werden. Wenn eine Collection diese Methode nicht unterstützt, darf der Iterator eine UnsupportedOperationException werfen.

Beispiel: Iterable und Iterator

ArrayList<String> list = new ArrayList<>();
list.add("Eintrag 1");
list.add("Eintrag 2");
list.add("Eintrag 3");

Iterable<String> iterable = list;

Iterator<String> iterator = iterable.iterator();

while (iterator.hasNext()) {
    String wert = iterator.next();
    System.out.println(wert);
}
Eintrag 1
Eintrag 2
Eintrag 3

Dieses Beispiel zeigt eine explizite Benutzung des Iterators mit einer while-Schleife. Die Klasse ArrayList werden wir später noch kennenlernen. Hier sei nur angemerkt, dass es sich um eine der Collection-Klassen handelt und mit add Elemente hinzugefügt werden können.

Nachdem der Iterator angelegt wurde, läuft die while-Schleife so lange durch die Elemente, bis kein weiteres mehr vorhanden ist (iterator.hasNext() liefert false). Das jeweils aktuelle Element wird von iterator.next() zurückgeliefert.

Die Zuweisung Iterable<String> iterable = list; dient nur dazu klarzumachen, dass ArrayList das Iterable-Interface implementiert. Normalerweise würde man sich diese sparen und eher schreiben:

ArrayList<String> list = new ArrayList<>();
list.add("Eintrag 1");
list.add("Eintrag 2");
list.add("Eintrag 3");

Iterator<String> iterator = list.iterator();

while (iterator.hasNext()) {
    String wert = iterator.next();
    System.out.println(wert);
}
ArrayList<String> list = new ArrayList<>();
list.add("Eintrag 1");
list.add("Eintrag 2");
list.add("Eintrag 3");

for (String wert : list) {
    System.out.println(wert);
}
Eintrag 1
Eintrag 2
Eintrag 3

Da die foreach-Schleife nicht nur mit Arrays, sondern mit allen Objekten umgehen kann, deren Klasse das Interface Iterable implementiert, kann man das Beispiel durch eine solche Schleife noch einmal verkürzen.

Dieses Beispiel ist semantisch äquivalent mit dem vorhergehenden. Es ist sogar so, dass der Java-Compiler die hier dargestellte foreach-Schleife in die vorher dargestellte while-Schleife umsetzt. Trotzdem ist die foreach-Variante übersichtlicher und daher vorzuziehen.

ListIterator

ListIterator erweitert Iterator um zusätzliche Methoden

  • boolean hasPrevious() – Gibt an, ob es noch vorhergehende Elemente gibt
  • T previous() – Geht zum vorhergehenden Element und gibt es zurück
  • int nextIndex() – Gibt die Position des nächsten Elements an
  • int previousIndex() - Gibt die Position des vorhergehenden Elements an
  • set(T element) – Ersetzt das aktuelle Element
  • add(T element) – Fügt hinter dem aktuellen Element ein weiteres ein

Ist nur auf bestimmten Collection-Arten möglich

Wenn die Collection in der Lage ist, den Objekten einen Index zuzuordnen, kann anstatt des Iterator der ListIterator verwendet werden. Dieser leitet von Iterator ab und fügt weitere Methoden hinzu.

Die add-Operation ist optional und muss vom konkreten Iterator nicht unterstützt werden.

Beispiel: ListIterator

ArrayList<String> list = new ArrayList<>();
list.add("Eintrag 1"); list.add("Eintrag 2"); list.add("Eintrag 3");

ListIterator<String> iterator = list.listIterator();
iterator.next(); iterator.next();
iterator.previous();
iterator.set("Neuer Eintrag 2");
iterator.next(); iterator.next();
iterator.add("Neuer Eintrag 4");
for (String wert : list) {
    System.out.println(wert);
}
Eintrag 1
Neuer Eintrag 2
Eintrag 3
Neuer Eintrag 4

Iterable und Iterator

Die Grafik zeigt noch einmal den Zusammenhang zwischen Iterable, Iterator und ListIterator. Wenn eine Klasse iterierbar sein soll, d. h. wenn man Elemente über einen Iterator aus der Klasse auslesen kann, muss sie das Interface Iterable implementieren. Wenn sie das Interface implementiert, so hat sie eine Methode iterator(), über die man einen Iterator anfordern kann. Eine erweiterte Form des Iterators wird durch den ListIterator zur Verfügung gestellt.

Die Trennung zwischen Iterable und Iterator ist wichtig, damit man zu einer Klasse mehrere Iteratoren erzeugen kann. Man kann also mehrfach iterator() aufrufen und dann mit den zurückgegebenen Objekten gleichzeitig über die Elemente der Collection laufen. Hierbei merkt sich jeder Iterator, an welcher Stelle er sich gerade befindet.

Eigenes Iterable schreiben

  • Man kann eigene Klassen in foreach-Schleifen verwenden
  • Hierzu implementiert die Klasse das Interface Iterable und stellt eine eigene Implementierung von Iterator zur Verfügung
    • als separate Klasse
    • als innere Klasse

Es ist nicht ungewöhnlich, mit einer selbstgeschriebenen Klasse das Interface Iterable zu implementieren, um durch die Elemente per foreach-Schleife laufen zu können. Hierzu muss man vor allem den Iterator schreiben, der von der iterator()-Methode zurückgegeben wird.

Für die Implementierung des Iterators stehen zwei Möglichkeiten zur Wahl: entweder man schreibt ihn als separate Klasse oder man greift, was deutlich einfacher ist, auf eine innere Klasse zurück.

Beispiel: Eigenes Iterable schreiben (Klasse)

public class IterableMitKlasse implements Iterable<String> {
    private String[] stringListe;
    ...
    public Iterator<String> iterator() {
        return new MyIterator(stringListe);
    }
}
class MyIterator implements Iterator<String> {
    private int pos = 0;
    private String[] stringListe;

    public MyIterator(String[] stringListe) {
        this.stringListe = stringListe;
    }

    public boolean hasNext() {
        return (pos < stringListe.length);
    }

    public String next() {
        return stringListe[pos++];
    }

    public void remove() { throw new UnsupportedOperationException(); }
}

Wählt man den Weg über eine getrennte Klasse, muss dem Iterator über den Konstruktor eine Referenz auf die Elemente übergeben werden (hier stringListe). Die Position in der Liste muss der Iterator selbst verwalten, damit es möglich ist, mehrere Iteratoren zu haben, die über dasselbe Objekt laufen.

Beispiel: Eigenes Iterable schreiben (innere Klasse)

public class IterableInnereKlasse implements Iterable<String> {
    private String[] stringListe;

    public Iterator<String> iterator() {
        return new Iterator<String>() {
            private int pos = 0;

            public boolean hasNext() {
                return (pos < stringListe.length);
            }
            public String next() {
                return stringListe[pos++];
            }
            public void remove() {
                throw new UnsupportedOperationException();
            }};
    }
}

Bei einer inneren Klasse (hier einer anonymen) ist der Zugriff auf die Daten des umgebenden Objektes einfacher, sodass der Iterator mit weniger Aufwand zu implementieren ist. Die Position muss er aber auch in diesem Fall selbst verwalten.


Copyright © 2025 Thomas Smits