15.02.2010
12:47

Die 80%-Schleife

Roman Seibold, Senior Consultant und technischer Leiter der Schulungsabteilung von aformatik

von Roman Seibold

 

 

 

Natürlich ist sie schon ein wenig älter, die "neue" for-Schleife, oder auch "enhanced-for-loop", sogar schon über fünf Jahre, aber sie lohnt dennoch einen genaueren Blick. Für alle, die sie noch nicht kennen sollten: seit Java 5 kann man mit einer vereinfachten Syntax über Arrays oder Collections iterieren. Beispiel:

    String[] array = { "enhanced", "for", "loop" };

for (String string : array)
{
System.out.println(string);
}

Eingebürgert hat sich auch ihr zweiter Name "for-each-loop", da die Lesart des obigen Konstrukts wie folgt lautet: "for each String string in array do..."

Hier stellt sich dem einen oder anderen schon die erste Frage: warum heisst das Schlüsselwort dann nicht "foreach" und warum muss ich einen Doppelpunkt machen, wenn man hier "in" liest? Diese Frage haben die Macher der Spezifikation (der JSR 201, der im übrigen auch Autoboxing, Enums, Varargs und static Imports enthält) gleich mit der Spezifikation beantwortet: "Die Kosten, neue Schlüsselwörter in der Sprache zu verankern, sind enorm. Außerdem entsteht eine Inkompatibilität zu existierendem Sourcecode, der die neuen Schlüsselwörter als Identifier benützt. Ganz konkret würde beispielsweise das Schlüsselwort 'in' stark in Konflikt mit 'System.in' geraten."

Ähnliches hat man ja auch erlebt, nachdem das Schlüsselwort 'enum' in Java 5 eingeführt wurde, da hat so manche Schleife, die mit einer Enumeration gearbeitet hatte, nicht mehr compiliert. Vermutlich wollte man uns mehr solcher Dinge ersparen.

Eine zweite Frage kommt einem in den Sinn: schön, dass ich nun über Arrays iterieren kann, aber warum kann ich dabei keine Änderungen vornehmen? Zur Illustration:

    String[] array = { "enhanced", "for", "loop" };

for (String string : array)
{
string = "changed";
}

Das compiliert zwar, erfüllt aber nicht den beabsichtigten Zweck überall im Array den String "changed" zu hinterlegen. Für Schreibzugriffe auf Collections und Arrays muss man nach wie vor mit einer klassischen for- oder while-Schleife arbeiten. Auch hier hat die Spezifikation schon wieder eine gute Begründung parat: "Die Expertengruppe hat über solche Fälle nachgedacht, dann aber für eine einfache und saubere Erweiterung gestimmt, die eine Sache macht, und die gut. Das Design folgt der 80-20-Regel (d.h. es löst 80% der Fälle mit 20% Aufwand). Wenn ein Problem in die restlichen 20% fällt, kann man es immer noch mit einem expliziten Iterator oder einem Index lösen, genau so wie man es in der Vergangenheit getan hat."

Jetzt, nach über 5 Jahren, kann man zurückblickend sagen, dass diese Entscheidung gut war. In vielen, vielen Fällen möchte man nur über Werte iterieren und mit diesen Werten dann eine andere Funktionalität aufrufen. Und das sind gefühlt schon diese 80%.

Und noch eine Frage mag sich stellen: wo ist denn eigentlich bei Collections mein Iterator hin? Das Beispiel von oben, in Collections übersetzt:

    String[] array = { "enhanced", "for", "loop" };
List<string> list = Arrays.asList(array);

for (String string : list)
{
System.out.println(string);
}

Hier taucht überhaupt kein Iterator auf. Die Lösung ist, dass in Wirklichkeit die enhanced-for-loop gar nicht mit Collections arbeitet, sondern nur mit Arrays und Instanzen von java.lang.Iterable. Iterable wiederum ist ein Interface, dass nur eine einzige Methode enthält, nämlich iterator(). In Java 5 wurde also die Eigenschaft, einen Iterator produzieren zu können aus dem Collection-Framework herausgelöst. Direkt mit einem Iterator arbeitet die neue for-Schleife auch überhaupt nicht, d.h.

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

for (String string : iterator)
{
System.out.println(string);
}

funktioniert nicht. Warum denn das nicht? Hier gibt es ebenfalls eine Erklärung, die sich in der Spezifikation findet. "Es gibt zwei Gründe, warum die foreach- Schleife nicht mit einem Iterator funktioniert: (1) Das Konstrukt würde nicht mehr wirklich viel syntaktische Verbesserung bringen und (2) die Ausführung der Schleife hätte den Seiteneffekt, dass sie den Iterator verbraucht."

Außerdem ist der Iterator ja genau das Artefakt in einer solchen Schleife, an dem man am wenigsten interessiert ist. Genaugenommen braucht man ihn ja nur für den Zweck, die Collection besuchen zu können, wie man die oft bemühte Indexvariable i häufig nur braucht, um auf ein Array in einer Schleife zugreifen zu können. Mit dem Iterator oder mit i selbst möchte man (in 80% der Fälle) eigentlich gar nichts machen, sie sind also nur unnötige technische Artefakte. Man vergleiche selbst:

      // 1                                 
for (String string : array)
{
System.out.println(string);
}

// 2
for (int i = 0; i < array.length; i++)
{
System.out.println(array[i]);
}

Im zweiten Beispiel muss man einiges tun. Man braucht eine Hilfsvariable i vom Typ int. Man muß wissen, dass Arrays ab 0 zählen. Man muss die Obergrenze definieren. Man darf nicht vergessen, i in jedem Schleifenschritt zu erhöhen. Und man muss den Elementzugriff mit dem Indexoperator [] durchführen. Klar, kalter Kaffee für erfahrene Java-Leute. Aber zugegeben, der Code mit der Hilfsvariablen ist mehr und auch etwas schwieriger zu lesen. Und vom Code-Verständnis bis zur Wartung ist nur noch ein kleiner Schritt.

Wer übrigens viele von diesen 80%-Schleifen in seinem Code hat und diesen vereinfachen möchte, dem kann ich nur Eclipse und sein "Clean Up" Wizard ans Herz legen, zu finden im Source-Menü. Dort gibt es die Option "Convert for loops to enhanced". Auf das obige Beispiel 2 angewendet erzeugt Eclipse bei mir folgenden Output:

    for (String element : array)
{
System.out.println(element);
}

Nett. Und wer nun selbst noch Nachforschungen betreiben möchte, findet die Spezifikation unter http://jcp.org/aboutJava/communityprocess/final/jsr201/index.html .

Weitere Erkenntnisse, Meinungen und Erfahrungen aus Projekten sind hier jederzeit gerne als Kommentare gesehen.

 

Bis dahin grüßt Sie (zu 100%)

 

Roman Seibold