Advanced   Java   Services for each Schleife Back Next Up Home

for each für Arrays

for each für Arrays primitiver Datentypen

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".


for each für Objektarrays

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.


for each für Containerklassen

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.


for each für eine eigene Containerklasse

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.


Containerklasse benutzt fremden Iterator

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(); 

Containerklasse ist selbst ein Iterator

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();

Containerklasse hat einen Iterator als innere Klasse

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()
      {
      }
   } 
}
Valid XHTML 1.0 Strict top Back Next Up Home