Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 11 Datenstrukturen und Algorithmen
gp 11.1 Mit einem Iterator durch die Daten wandern
gp 11.1.1 Die Schnittstellen Enumeration und Iterator
gp 11.1.2 Arrays mit Iteratoren durchlaufen
gp 11.2 Datenstrukturen und die Collection-API
gp 11.2.1 Die Schnittstelle Collection
gp 11.2.2 Das erste Programm mit Container-Klassen
gp 11.2.3 Schnittstellen, die Collection erweitern, und Map
gp 11.2.4 Konkrete Container-Klassen
gp 11.3 Listen
gp 11.3.1 AbstractList
gp 11.3.2 Beispiel mit List-Methoden
gp 11.3.3 ArrayList
gp 11.3.4 asList() und die »echten« Listen
gp 11.3.5 toArray() von Collection verstehen - die Gefahr einer Falle erkennen
gp 11.3.6 Die interne Arbeitsweise von ArrayList und Vector
gp 11.3.7 LinkedList
gp 11.3.8 Queue, die Schlange
gp 11.4 Stack (Kellerspeicher, Stapel)
gp 11.4.1 Die Methoden von Stack
gp 11.4.2 Ein Stack ist ein Vector - aha!
gp 11.5 Die Klasse HashMap und assoziative Speicher
gp 11.5.1 Ein Objekt der Klasse HashMap erzeugen
gp 11.5.2 Einfügen und Abfragen der Datenstruktur
gp 11.5.3 Wichtige Eigenschaften von Assoziativspeichern
gp 11.5.4 Elemente im Assoziativspeicher müssen unveränderbar bleiben
gp 11.5.5 Die Arbeitsweise einer Hash-Tabelle
gp 11.5.6 Aufzählen der Elemente
gp 11.5.7 Der Gleichheitstest und der Hash-Wert einer Hash-Tabelle
gp 11.5.8 Klonen
gp 11.6 Die abstrakte Klasse Dictionary
gp 11.7 Die Properties-Klasse
gp 11.7.1 Über die Klasse Properties
gp 11.7.2 put(), get() und getProperties()
gp 11.7.3 Eigenschaften ausgeben
gp 11.7.4 Systemeigenschaften der Java-Umgebung
gp 11.7.5 Browser-Version abfragen
gp 11.7.6 Properties von der Konsole aus setzen
gp 11.7.7 Windows-typische INI-Dateien
gp 11.8 Algorithmen in Collections
gp 11.8.1 Datenmanipulation: Umdrehen, Füllen, Kopieren
gp 11.8.2 Vergleichen von Objekten mit Comparator und Comparable
gp 11.8.3 Größten und kleinsten Wert einer Collection finden
gp 11.8.4 Sortieren
gp 11.8.5 nCopies()
gp 11.8.6 Singletons
gp 11.8.7 Elemente in der Collection suchen
gp 11.9 Synchronisation der Datenstrukturen
gp 11.10 Typsichere Datenstrukturen
gp 11.11 Die abstrakten Basisklassen für Container
gp 11.11.1 Optionale Methoden
gp 11.12 Die Klasse BitSet für Bitmengen
gp 11.12.1 Ein BitSet anlegen und füllen
gp 11.12.2 Mengenorientierte Operationen
gp 11.12.3 Funktionsübersicht
gp 11.12.4 Primzahlen in einem BitSet verwalten
gp 11.13 Ein Design-Pattern durch Beobachten von Änderungen
gp 11.13.1 Design-Pattern
gp 11.13.2 Das Beobachter-Pattern (Observer/Observable)


Galileo Computing

11.1 Mit einem Iterator durch die Daten wanderndowntop

Wir wollen bei den Datenstrukturen eine Möglichkeit kennen lernen, wie sich die gespeicherten Daten unabhängig von der Implementierung immer mit derselben Technik abfragen lassen. Bei den Datenstrukturen handelt es sich meistens um Daten in Arrays, Bäumen oder Ähnlichem. Oft wird nur die Frage nach der Zugehörigkeit eines Werts zum Datenbestand gestellt, also: »Gehört das Wort dazu?«. Dieses Wortproblem ist durchaus wichtig, aber die Möglichkeit, die Daten in irgendeiner Weise aufzuzählen, ist nicht minder bedeutend. Bei Arrays können wir über den Index auf die Elemente zuzugreifen. Da wir jedoch nicht immer ein Array als Datenspeicher haben und uns auch die objektorientierte Programmierung verbietet, hinter die Kulisse zu sehen, benötigen wir möglichst einen allgemeineren Weg. Hier bieten sich Enumeratoren beziehungsweise Iteratoren an.


Galileo Computing

11.1.1 Die Schnittstellen Enumeration und Iteratordowntop

Für Iteratoren definiert die Java-Bibliothek zwei unterschiedliche Schnittstellen. Das hat historische Gründe. Die Schnittstelle Enumeration gibt es seit den ersten Java-Tagen; die Schnittstelle Iterator gibt es seit Java 1.2.

Die Schnittstelle Enumeration

Enumeration schreibt zwei Funktionen hasMoreElements() und nextElement() vor, mit denen durch einen Datengeber (in der Regel eine Datenstruktur) iteriert werden kann - wir sprechen in diesem Fall auch von einem Iterator. Bei jedem Aufruf von nextElement() erhalten wir ein weiteres Element der Datenstruktur. Im Gegensatz zum Index eines Felds können wir ein Objekt nicht noch einmal auslesen, nicht vorlaufen beziehungsweise hin und her springen. Ein Iterator gleicht anschaulich einem Datenstrom; wollten wir ein Element zweimal besuchen, zum Beispiel von rechts nach links noch einmal durchwandern, dann müssen wir wieder ein neues Enumeration-Objekt erzeugen oder uns die Elemente zwischendurch merken.

Abbildung
Hier klicken, um das Bild zu Vergrößern


interface java.util.Enumeration

gp boolean hasMoreElements()
Testet, ob noch ein weiteres Element aufgezählt werden kann.1
gp Object nextElement()
Liefert das nächste Element der Enumeration zurück. Diese Funktion kann eine NoSuchElementException auslösen, wenn nextElement() aufgerufen wird und das Ergebnis false beim Aufruf von hasMoreElements() ignoriert wird.

Beispiel Die Aufzählung erfolgt meistens über einen Zweizeiler: Nehmen wir an, die Datenstruktur ds besitzt eine Methode elements(), die ein Enumeration-Objekt zurückgibt.
for ( Enumeration e = ds.elements(); e.hasMoreElements(); )
 System.out.println( e.nextElement() );

Die Schnittstelle Iterator

Ein Iterator ist für die neuen Collection-Klassen das, was Enumeration für die herkömmlichen Datenstruktur-Klassen ist. Die Schnittstelle Iterator besitzt kürzere Methodennamen als Enumeration. Nun heißt es hasNext() an Stelle von hasMoreElements() und next() an Stelle von nextElement(). Übergehen wir ein false von hasNext(), so erhalten wir wiederum eine NoSuchElementException. Zudem besitzt ein Iterator auch die Möglichkeit, das zuletzt aufgezählte Element aus dem zugrunde liegenden Container zu löschen. Dazu dient die optionale Methode remove(); sie lässt sich allerdings nur unmittelbar aufrufen, nachdem next() das zu löschende Element als Ergebnis geliefert hat. Eine Enumeration kann die aufgezählte Datenstruktur grundsätzlich nicht verändern.

Abbildung
Hier klicken, um das Bild zu Vergrößern


interface java.util.Iterator

gp boolean hasNext()
Liefert true, falls die Iteration weitere Elemente bietet.
gp Object next()
Liefert das nächste Element in der Aufzählung oder NoSuchElementException, wenn keine weiteren Elemente mehr vorhanden sind.
gp void remove()
Entfernt das Element, das der Iterator zuletzt bei next() geliefert hat. Implementiert ein Iterator diese Funktion nicht, so löst er eine UnsupportedOperationException aus.

Hinweis Es ist eine interessante Frage, warum es die Methode remove() im Iterator gibt. Die Erklärung dafür ist, dass der Iterator die Stelle kennt, an der sich die Daten befinden (eine Art Cursor). Darum können die Daten auch effizient direkt dort gelöscht werden. Das erklärt jedoch nicht unbedingt, warum es keine Einfüge-Methode gibt. Ein allgemeiner Grund mag sein, dass bei vielen Container-Typen das Einfügen an einer bestimmten Stelle keinen Sinn ergibt, etwa bei SortedSet, SortedMap, Set und Map. Dort ist die Einfügeposition durch die Sortierung vorgegeben oder belanglos (beziehungsweise bei HashSet durch die interne Realisierung bestimmt), also kein Fall für einen Iterator. Dazu wirft Einfügen weitere Fragen auf: Vor oder nach dem zuletzt per next() gelieferten Element? Soll das neue Element mit aufgezählt werden oder nicht? Auch dann nicht, wenn es in der Sortierung erst später an die Reihe käme? Eine Löschen-Methode ist problemloser und universell anwendbar.


Galileo Computing

11.1.2 Arrays mit Iteratoren durchlaufentoptop

Die Konzepte Array und Container-Objekte passen oft nicht genau zusammen, da zwischen ihnen ein Bruch in der Programmierung liegt. Beide werden unterschiedlich angesprochen: ein Container häufig mittels Iteratoren, ein Array direkt über einen ganzzahligen Index. Wenn es nicht auf Geschwindigkeit ankommt, sollten wir als Container besser eine Datenstruktur verwenden und kein »rohes« Array. Bei einem Array müssen wir uns immer selbst um Strategien zum Durchlaufen der Array-Elemente kümmern, bei Datenstrukturen haben wir das Konzept der Enumeratoren und Iteratoren. Gut ist es, ein Array nachträglich mit derselben Abstraktion auszustatten wie eine Datenstruktur, also mit einem Iterator. Folgende Implementierung soll uns dabei helfen, von den Vorteilen eines Iterators zu profitieren. Dadurch kann zum Beispiel ein Array leichter gegen eine mächtigere Datenstruktur ausgetauscht werden. Wir müssen dazu nur für drei Methoden Programmcode bereitstellen: hasNext(), next() und remove(). Für Letztere wollen wir keine Implementierung bieten und eine UnsupportedOperationException auslösen, da beim Löschen eigentlich auch das Feld kleiner werden muss.

Damit sieht die Klasse ArrayIterator wie folgt aus:

Listing 11.1 ArrayIterator.java

import java.util.*;
public class ArrayIterator implements Iterator
{
  private Object array[];
  private int    pos = 0;
   public ArrayIterator( Object anArray[] )
  {
    array = anArray;
  }
   public boolean hasNext()
  {
    return pos < array.length;
  }
  public Object next() throws NoSuchElementException
  {
    if ( hasNext() )
      return array[pos++];
    else
      throw new NoSuchElementException();
  }
  public void remove()
  {
    throw new UnsupportedOperationException();
  }
}

Ein ArrayIterator wird über einen parametrisierten Konstruktor für ein bestimmtes Array-Objekt erzeugt. Die Funktion nextElement() löst eine NoSuchElementException aus, wenn das Ergebnis false von hasMoreElements() ignoriert wird. NoSuchElementException ist eine RuntimeException, so dass sie nicht ausdrücklich aufgefangen werden muss.

Das Array kann parallel im Hintergrund noch verändert werden. Da sich die Größe allerdings nicht mehr ändern kann, müssen wir die ersten beiden kritischen Zeilen in next() nicht synchronisieren.

Praktisch bei dem ArrayIterator ist nun, dass er an alle Funktionen weitergegeben werden kann, die einen Iterator als Parameter erwarten und kein remove() verwenden. Sonst hätten wir eine andere Datenstruktur wählen müssen.

Folgendes Beispiel zeigt unseren neuen Iterator im Einsatz beim Aufzählen der Kommandozeilen-Argumente:

static public void main( String arg[] )
{
  Iterator i = new ArrayIterator( arg );
  while ( i.hasNext() )
    System.out.println( "Eintrag: " + i.next() );
}





1 Enumeratoren (und Iteratoren) können nicht serialisiert werden, da sie die Schnittstelle Serializable nicht implementieren.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de