06.08 Iterable, for-each und Iterator

Aus den Kapiteln 02.09 Arrays und 02.08 Schleifen kennen Sie bereits die – seit Java 1.5 neue – for-each-Schleife. Doch woher nimmt die for-each-Schleife die Daten, über die iteriert werden soll? Dafür sind Implementationen von Iterable und Iterator nötig.

Die for-each-Schleife iteriert

Iterieren bedeutet in diesem Fall, dass die Elemente eines Objekts (z. B. einer Liste) nacheinander durchgegangen werden.

über einen Iterator, welcher von einer Klasse, die das Interface Iterable implementiert, bereit gestellt wird. Aber sehen wir uns zuerst die interne Verarbeitung dieser for-each Schleife an:

String[] str = {"1", "2", "3"};
for (String s : str) {
  System.out.println(s);
}

Beim Kompilieren wird aus dieser Schleife folgender Code:

String args1[] = {"1", "2", "3"};
String args2[] = args1;
int i = args2.length;
for(int j = 0; j < i; j++) {
  String s = args2&#91;j&#93;;
  System.out.println(s);
}&#91;/sourcecode&#93;

Sie sehen also, dass die for-each-Schleife in eine gewöhnliche Schleife umgewandelt wird.

In diese for-each Schleife kann, wie bereits erwähnt, alles gestellt werden, was <code>Iterable</code> implementiert. <code>Iterable</code> ist ein generisches Interface und erwartet, dass die Methode <code>iterator</code> überschrieben wird, welche einen ebenfalls generischen <code>Iterator</code> zurück gibt. Wie so ein <code>Iterator</code> aussieht wird später erläutert. Zuerst programmieren wir eine Klasse <code>MyIterable</code>, die <code>Iterable</code> implementiert, und in der Methode <code>iterator</code> vorerst <code>null</code> zurück gibt.

import java.util.Iterator;

public class MyIterable<T> implements Iterable<T> {

  public Iterator<T> iterator() {
    return null;
  }
}

Wir können nun ein Objekt dieser Klasse in einer for-each-Schleife verwenden.

MyIterable<String> myit = new MyIterable<String>();
for (String s : myit) {
  System.out.println(s);
}

Selbstverständlich endet die Ausführung dieses Codes in einer NullPointerException, da wir keinen Iterator zurückgeben, sondern lediglich null. Aber das ist erstmal egal. Sehen wir uns nun an, was der Compiler aus diesem Code macht:

MyIterable myiterable = new MyIterable();
String s;
for(Iterator iterator = myiterable.iterator(); iterator.hasNext(); System.out.println(s))
  s = (String)iterator.next();

Sie sehen auch hier, dass die for-each-Schleife in eine gewöhnliche, optimierte for-Schleife umgewandelt wurde. Um den generierten Code zu verstehen, betrachten wir das Interface Iterator an.

Ein Iterator ist hier eine Art Aufzählung oder Auflistung von Daten und besitzt die Methoden hasNext, next und remove. hasNext gibt einen boolean zurück, ob noch weitere Elemente in dieser Auflistung vorhanden sind. Mit next wird das nächste Element abgefragt. Falls kein Element mehr vorhanden ist, wird eine NoSuchElementException ausgelöst. Und remove entfernt das aktuelle Element aus dem Iterator. Soll es nicht möglich sein ein Element aus dem Iterator zu löschen, kann man website eine UnsupportedOperationException werfen. Außerdem wirft remove noch eine IllegalStateException, falls das aktuelle Element bereits entfernt, oder next noch nicht aufgerufen wurde.

Implementieren wir nun selbst einen simplen Iterator, dem die Daten in einer Methode übergeben werden:

import java.util.Iterator;
import java.util.NoSuchElementException;

public class MyIterator<E> implements Iterator<E> {

  private int position = -1;
  private E[] arr = null;

  public void setData(E[] elements) {
    this.arr = elements;
  }

  public boolean hasNext() {
    return this.position + 1 < this.arr.length;
  }

  public E next() throws NoSuchElementException {

    if (!hasNext()) {
      throw new NoSuchElementException("No more elements");
    }
    return this.arr&#91;++this.position&#93;;
  }

  public void remove() throws UnsupportedOperationException {
    throw new UnsupportedOperationException("Operation is not supported");
  }
}&#91;/sourcecode&#93;

Mit dem Attribut <code>position</code> geben wir an, wo wir uns momentan befinden. Dies ist wichtig um das richtige Element unseres <code>Iterators</code> zurückgeben, und die Frage von <code>hasNext</code>, ob sich noch mehr Elemente im <code>Iterator</code> befinden, beantworten zu können. Der Initialisierungswert von <code>position</code> ist -1, da <code>next</code> das <strong>nächste Element</strong> zurück gibt. Folglich darf der Zeiger <code>position</code> noch nicht zu Beginn auf das erste Element deuten.

Wenn jetzt noch <code>MyIterable</code> und der Aufruf angepasst werden, haben wir ein lauffähiges Beispiel:

import java.util.Iterator;

public class MyIterable<T> implements Iterable<T> {

  private T[] elements = null;

  public void setElements(T[] elements) {
    this.elements = elements;
  }

  public Iterator<T> iterator() {
    MyIterator<T> iter = new MyIterator<T>();
    iter.setData(this.elements);
    return iter;
  }
}
MyIterable<String> myit = new MyIterable<String>();
myit.setElements(new String[] {"1", "2", "3"});
for (String s : myit) {
  System.out.println(s);
}

Hiermit haben Sie eine eigene Klasse geschrieben, die über die for-each-Schleife verarbeitet werden kann.

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.