Advanced Java Services | for each Schleife |
Geben wir uns zunächst ein Array mit einem primitiven Datentyp vor:
int[] arr = { 4, 5, 6, 2, 3, 4 } ;
Die bisherige Form der for-Schleife stammt zwar von C++ ab, ist aber konzeptionell sehr viel älter und stammt sozusagen aus der Gründerzeit der Programmierung. Die folgende Ausgabe eines Arrays ist zwar gut lesbar, aber doch relativ aufwendig:
// bisher for(int i=0 ; i<arr.length; i++) System.out.println(arr[i]);
Die neue Syntax, die u.a. in C# bereits seit längerem verwendet wird, ist stark vereinfacht und trotzdem gut, wenn nicht sogar besser lesbar. Man spricht hier gerne von einer for each Schleife, obwohl Java each (gottseidank) nicht als neues Schlüsselwort einführt.
// neu ab 1.5
for(int elem : arr)
System.out.println(elem);
Den Kopf der erweiterten for-Schleife liest man am besten als "für jedes elem(-ent) in arr".
Es spielt keine Rolle, welchen Typ das Array hat, auch für Arrays aus Objekten funktioniert die erweiterte for-Schleife genauso. Das folgende Beispiel definiert ein kleines Array von Strings und gibt diese auf die Konsole aus.
String[] arr = { "null", "eins", "zwei", "drei" };
// alt
for (int i=0; i < arr.length; i++ )
System.out.println(arr[i]);
// neu ab 1.5
for(String elem : arr)
System.out.println(elem);
Natürlich kann man auch Instanzen selbstgeschriebener Klassen zu einem Array zusammenfassen und mit foreach abhandeln. Das folgende Beispiel bezieht sich auf die Klasse Person und geht davon aus, daß die Klasse über eine Methode println() verfügt, mit der man Vor- und Nachnamen einer Person auf die Konsole ausgeben kann. Hier wird sehr deutlich, welche Vereinfachung die neue Form der Schleife darstellt.
Person[] parr = { new Person("Rose", "Ausländer") ,
new Person("Heinrich", "Heine") ,
new Person("Else", "Lasker-Schüler") };
// alt
for (int i=0; i< parr.length; i++ )
parr[i].println();
// neu ab 1.5
for(Person elem : parr)
elem.println();
Im Hintergrund wird immer noch die alte Schleifenform verwendet. Der neue Compiler kann eben die neue Syntax verstehen und in die alte übersetzen.
Ein wenig raffinierter wird die Sache, wenn wir, was möglich ist, for each bei Containerklassen einsetzen. Wir verzichten zunächst auf das neue Feature Generics einzusetzen und füllen eine HashSet mit einigen Elementen. Die einzige Möglichkeit, die Elemente der HashSet auszugeben, besteht darin, sich mit der Methode iterator() ein Iteratorobjekt liefern zu lassen:
// zunächst ohne generics HashSet hs = new HashSet(); hs.add("hermann"); hs.add("else"); hs.add("kurt"); hs.add("heinrich"); for (Iterator it = hs.iterator() ; it.hasNext() ; ) System.out.println( it.next() );
Aber auch in dieser Situation kann man for each einsetzen:
// neu ab 1.5
for (Object elem : hs)
System.out.println(elem);
In dieser Situation auf der Set-Seite der Collection-Hierarchie gibt es keinen Index. Nur über den
Iterator kommt man auf die Elemente des Containers HashSet zugreifen. Der Compiler übersetzt also die
neue Syntax in die ältere Form mit Iterator, eine andere Möglichkeit gibt es nicht. Mit anderen Worten,
for each für Containerklassen funktioniert nur, wenn es eine Methode iterator() gibt. Und woher
weiß der Compiler, ob es eine solche Methode gibt ? Ganz einfach, man nehme ein Interface, das die
Methode iterator() vereinbart. Die Containerklassen, die dieses Interface implementieren haben dann
eine Methode iterator(). Konsequenterweise gibt es ab der Version 1.5 eben ein Interface Iterable,
das nun als SuperInterface von Collection fungiert. Also kann man u.a. für alle Ableitungen von
Collection die foreach-Schleife einsetzen.
Hier nochmal das obige Beispiel mit generics:
// diesmal mit generics, neu ab 1.5 HashSet<String> hs = new HashSet<String>(); hs.add("hermann"); hs.add("else"); hs.add("kurt"); hs.add("heinrich"); for (Iterator<String> it = hs.iterator() ; it.hasNext() ; ) System.out.println( it.next() );
Und nun mit for each:
// neu ab 1.5
for (String elem : hs)
System.out.println(elem);
Die Schreibweise mit <String> ist zugegebenermaßen etwas aufwendiger, dafür bekommt man aber Typsicherheit zur Compilezeit, denn durch diese neue Syntax ist der Compiler in der Lage zu prüfen, ob in die HashSet versehentlich mal etwas anderes aufgenommen wird als ein String. Setzt man die foreach Schleife konsequent ein, spart man sich ja auch die Typangabe bei Iterator.
Will man in einer selbstgeschriebenen Containerklasse foreach einsetzen muß man konsequenterweise
das Interface Iterable implementieren, also einen Iterator schreiben und ihn über die Methode
iterator() zurückliefern. Genau das zeigen wir hier. Wir nehmen als Ausgangspunkt die Klasse
Person und schreiben uns eine Containerklasse Persons, die das Interface Iterable implementiert.
Holt man sich mit getClass().getName() den Typ des Iteratorobjekts von HashSet, so erhält man übrigens
"java.util.HashMap$KeyIterator". Der Iterator wird in HashSet also als innere Klasse realisiert.
Wir realisieren hier unsere Containerklasse Persons in mehreren Varianten.
Dies ist der einfachste Fall. Die Containerklasse verwaltet eine private ArrayList und benutzt deren Iterator.
import java.util.*;
public class Persons1 implements Iterable<Person>
{
private ArrayList<Person> list ;
public Persons1()
{
this.list = new ArrayList<Person>();
}
public void add(Person p)
{
list.add(p);
}
public Iterator<Person> iterator()
{
return list.iterator();
}
}
Nun kann man foreach wie folgt einsetzen:
Person p = new Person("hermann","hesse");
Person p2 = new Person("rainer maria","rilke");
Persons1 plist = new Persons1() ;
plist.add(p);
plist.add(p2);
for(Person pers : plist)
pers.println();
Es ist wichtig, daß man bei implements schreibt implements Iterable<Person>. Vergißt man hier den Typ anzugeben und scheibt lediglich implements Iterable, ist das kein Syntaxfehler. Man kann jedoch später die foreach-Schleife nicht optimal benutzen. Da die Typinformation fehlt, muß man dann folgendermaßen codieren:
for(Object pers : plist)
((Person)pers).println();
Verwenden wir in Persons keine Collectionklasse, sondern ein Array, so können wir uns keinen fremden Iterator leihen, sondern müssen selbst einen schreiben. Dazu müssen die drei Methoden hasNext(), next() und remove() codiert werden. remove() hat hier keine Funktionalität.
import java.util.*;
public class Persons2 implements Iterable<Person>, Iterator<Person>
// fehlt bei Iterator das <Person>, dann compilerwarning : uses unchecked or unsafe operations
{
private Person[] arr;
private int iteratorIndex=-1, lastIndex=-1;
public Persons2()
{
arr = new Person[10] ;
}
public void add(Person p)
{
lastIndex++;
if( lastIndex>arr.length-1 ) // kein platz mehr im array
{
//neues array anlegen
Person[] arr2 = new Person[arr.length+10];
// altes array in neues kopieren
System.arraycopy(arr, 0, arr2, 0, arr.length) ;
// alten zeiger auf neues array setzen
arr = arr2;
}
// neues element dazu
arr[lastIndex] = p;
}
public Iterator<Person>iterator()
// fehlt bei Iterator das <Person>, dann compilerwarning :
// uses unchecked or unsafe operations
{
// Klasse selbst ist der Iterator
return this;
}
public boolean hasNext()
{
if ( iteratorIndex<lastIndex )
return true;
// damit der iterator nochmal durchlaufen werden kann.
iteratorIndex=-1 ;
return false ;
}
public Person next()
{
return arr[++iteratorIndex];
}
public void remove()
{
}
// for convenience
public int length()
{
return lastIndex+1;
}
public Person get(int i)
{
return arr[i];
}
}
Wie vorher kann man nun schreiben:
Person p = new Person("hermann","hesse"); Person p2 = new Person("rose","auslaender"); Persons2 plist = new Persons2() ; plist.add(p); plist.add(p2); for(Person pers : plist) pers.println();
Zur Übung verlagern wir nun den Iterator in eine innere Klasse.
import java.util.*;
public class Persons3 implements Iterable<Person>
{
private Person[] arr;
private int lastIndex=-1;
public Persons3()
{
arr = new Person[10] ;
}
public void add(Person p)
{
lastIndex++;
if( lastIndex>arr.length-1 ) // kein platz mehr im array
{
//neues array anlegen
Person[] arr2 = new Person[arr.length+10];
// altes array in neues kopieren
System.arraycopy(arr, 0, arr2, 0, arr.length) ;
// alten zeiger auf neues array setzen
arr = arr2;
}
// neues element dazu
arr[lastIndex] = p;
}
public Iterator<Person> iterator()
// fehlt bei Iterator das <Person> , dann compilerwarning :
// uses unchecked or unsafe operations
{
return new PersonIterator();
}
private class PersonIterator implements Iterator<Person>
// fehlt bei Iterator das <Person> , dann compilerwarning :
// uses unchecked or unsafe operations
{
private int iteratorIndex=-1;
public boolean hasNext()
{
if ( iteratorIndex<lastIndex )
return true;
// damit der iterator nochmal durchlaufen werden kann.
iteratorIndex=-1 ;
return false ;
}
public Person next()
{
return arr[++iteratorIndex];
}
public void remove()
{
}
}
}