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.10 Typsichere Datenstrukturentoptop

Die älteren Datenstrukturen Vector, Stack und Hashtable sowie die neuen aus der Collection-API haben einen großen Nachteil, den C++-Freunde an Java bemängeln. Die Elemente der Datenstrukturen sind immer vom Typ Object, und Typsicherheit über Templates, wie C++ sie bietet, ist bisher nicht vorgesehen. In der nächsten Java-Generation 1.5 werden generische Typen in die Sprache Einzug halten; ein Compiler mit generischen Typen ist bei Sun verfügbar. Das Vorhaben wird genauer in der Java Specification Requests 14, »Add Generic Types To The Java Programming Language«, beschrieben.

Eine vollständige Kapselung

Um die Nimm-alle-Objekte-auf-Datenstrukturen so anzupassen, dass nur bestimmte Elemente eingefügt und herausgenommen werden dürfen, gibt es mehrere Ansätze. Eine Lösung wäre die interne Benutzung einer allgemeinen Datenstruktur für beliebige Objektreferenzen und die entsprechende Delegation an genau diese Datenstruktur. Das wäre etwa für eine verkettete Liste von Socken:

class SockenListe
{
  private LinkedList l;

  public void add( Socke s )
  {
    l.add( s );
  }
  ...
}

Diese Technik hat den großen Vorteil, dass sie wirklich nur Elemente vom Typ Socke akzeptiert, dabei aber auch den Nachteil, dass wir

gp alle Methoden neu implementieren müssen und die Anfragen an die allgemeine Liste delegieren und
gp dass SockenListe nicht mehr als Liste durchgeht - Collection implementieren macht keinen Sinn, die Typen müssen dann Object sein - und keine der Methoden, etwa von Collections, anwendbar ist. SockenListe hat ja nichts mehr mit den Schnittstellen gemeinsam.

Durch diese Nachteile lässt sich eine typsichere Liste nicht im großen Stil verwirklichen, insbesondere, da wir viel zu programmieren haben.

Unterklassen bilden

Eine andere einfache Lösung besteht in der Implementierung einer Unterklasse, die dann die kritischen Methoden mit den Eingabe- und Ausgabeparametern implementiert und die alten Methoden mit dem Parameter- beziehungsweise Ergebnis-Typ Object ausschaltet. So kann etwa add() eine UnsupportedOperationException auswerfen, um anzuzeigen, dass Object nicht erlaubt ist. Leider ist die Überprüfung nur auf Laufzeitebene möglich, und dies ist meistens nicht erwünscht.

class SockenListe extends LinkedList
{
  public void add( Object o )
  {
    throw new UnsupportedOperationException( "No add. Only Socks" );
  }
  public void add( Socke e )
  {
    super.add( o );
  }
}

Eine SockenList ist nun auch eine Form der Liste und genießt somit alle Funktionalität aus der Collection-Klasse. Doch immer noch können beliebige Objekte einem add() übergeben werden. Der Compiler merkt dies nicht; die Rechnung kommt später durch eine Laufzeit-Exception.

Problematischer ist die Unterklassenbildung jedoch bei den Anfrage-Funktionen. Sie müssten den Typ Socke liefern, doch Java erlaubt es (bisher) nicht, dass eine Klasse eine Funktion mit einem anderen Rückgabetyp überschreibt. Das müsste hier aber gemacht werden: SockenListe müsste bei get(int index) den Typ Socke liefern, aber nicht Object. Die einzige Lösung wäre eine zweite Funktion getSocke(int index), aber dann haben wir natürlich wieder eine ganz andere Schnittstelle. Ein Teufelskreis!

Einen typsicheren Iterator für den Zugriff

Unter Umständen muss auch gar nicht die gesamte Schnittstelle typsicher sein, sondern es reicht ein eigener Iterator, der Objekte eines gewissen Typs liefert. Dann lässt sich intern eine Standarddatenstruktur mit allgemeinem Typ verwenden, die nach außen über einen eigenen Iterator den gewünschten Typ zurückgibt.

Nehmen wir an, wir wollten einen Iterator DateIterator für java.util.Date-Objekte implementieren, damit er die in einer java.util.ArrayList gespeicherten Datums-Objekte vom Typ Date nach außen weitergibt. Der Iterator könnte dann die Methode hasNext() wie bekannt implementieren und auch next(). Doch bei next() ist es wichtig, darauf zu achten, dass der Rückgabetyp nicht Object ist, sondern Date. Damit fällt leider auch eine wichtige Schnittstelle aus, denn java.util.Iterator kann der DateIterator nicht mehr implementieren, denn Iterator schreibt Object als Rückgabetyp vor. Damit haben wir Typsicherheit gewonnen, können aber leider nicht mehr über die bekannte Schnittstelle gehen.





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