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.2 Datenstrukturen und die Collection-APIdowntop

Dynamische Datenstrukturen passen ihre Größe der Anzahl der Daten an, die sie aufnehmen. Die meisten Datenstrukturen sind dynamisch, haben aber dadurch den Nachteil, dass zur Laufzeit Speicher angefordert werden muss, wenn Daten eingefügt werden. Dieses Problem wurde mittlerweile in der Informatik erkannt, und der Trend geht wieder zu festen, nicht dynamischen Datenstrukturen - natürlich nur dort, wo dies möglich ist. Nicht dynamische Datenstrukturen sind beispielsweise Arrays, die einmal fest in einer bestimmten Größe erzeugt sind.

Eine der größten Neuerungen, die die Java-2-Plattform eingeführt hat, ist die Collection-API. Ein Container ist ein Objekt, welches wiederum Objekte aufnimmt und die Verantwortung für die Elemente übernimmt. Wir werden die Begriffe »Container« und »Collection« synonym verwenden.


Galileo Computing

11.2.1 Die Schnittstelle Collectiondowntop

Collection ist die Basis der Hierarchie. Alle Container-Klassen - bis auf die Assoziativspeicher - implementieren die Schnittstelle Collection und erhalten damit einen gemeinsamen, äußeren Rahmen. Mit den dort definierten Operationen lassen sich Elemente hinzufügen, löschen, selektieren und finden.

Die Collection-Schnittstelle wird von mehreren Schnittstellen erweitert. Abgeleitete Schnittstellen schreiben Verhalten vor, ob etwa der Container Werte doppelt beinhalten darf oder die Werte sortiert hält; List, Set und SortedSet sind dabei die wichtigsten. AbstractCollection implementiert Basisfunktionalität und belässt nur noch zwei abstrakte Funktionen.

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


interface java.util.Collection

gp boolean add( Object o )
Optional. Fügt dem Container ein Element hinzu und gibt true zurück, falls sich das Element einfügen lässt. Gibt false zurück, falls schon ein Objekt gleichen Werts vorhanden ist und doppelte Werte nicht erlaubt sind. Erlaubt der Container das Hinzufügen nicht, muss eine UnsupportedOperationException ausgeworfen werden.
gp boolean addAll( Collection c )
Fügt alle Elemente der Collection c dem Container hinzu.
gp void clear()
Optional. Löscht alle Elemente im Container. Wird dies vom Container nicht unterstützt, wird eine UnsupportedOperationException ausgeworfen.
gp boolean contains( Object o )
Liefert true, falls der Container ein inhaltlich gleiches Element enthält.
gp boolean containsAll( Collection c )
Liefert true, falls der Container alle Elemente der Collection c enthält.
gp boolean isEmpty()
Liefert true, falls der Container keine Elemente enthält.
gp Iterator iterator()
Liefert ein Iterator-Objekt über alle Elemente des Containers.
gp boolean remove( Object o )
Optional. Entfernt das angegebene Objekt aus dem Container, falls es vorhanden ist.
gp boolean removeAll( Collection c )
Optional. Entfernt alle Objekte der Collection c aus dem Container.
gp boolean retainAll( Collection c )
Optional. Entfernt alle Objekte, die nicht in der Collection c vorkommen.
gp int size()
Gibt die Anzahl der Elemente im Container zurück.
gp Object[] toArray()
Gibt ein Array mit allen Elementen des Containers zurück.
gp Object[] toArray( Object a[] )
Gibt ein Array mit allen Elementen des Containers zurück. Verwendet das als Parameter übergebene Array, wenn es groß genug ist. Sonst wird ein Array passender Größe angelegt, dessen Laufzeittyp a entspricht.
gp boolean equals( Object o )
Prüft, ob das angegebene Objekt ebenfalls ein Container ist und die gleichen Elemente enthält wie dieser Container.
gp int hashCode()
Liefert den Hash-Wert des Containers. Dies ist interessant, wenn der Container als Schlüssel in Hash-Tabellen verwendet wird. Dann darf der Inhalt aber nicht mehr geändert werden, da der Hash-Wert von allen Elementen des Containers abhängt.

Galileo Computing

11.2.2 Das erste Programm mit Container-Klassendowntop

Wie wir schon gesehen haben, implementieren alle Container-Klassen das Interface Collection und haben dadurch schon wichtige Funktionen, um Daten aufzunehmen, zu manipulieren und auszulesen. Das folgende Programm erzeugt als Datenstruktur eine verkettete Liste und fügt zehn String-Elemente ein. Diese werden mit einem Iterator wieder ausgelesen.

Listing 11.2 ErsteSammlung.java

import java.util.*;
class ErsteSammlung
{
  public static void main( String args[] )
  {
    Collection c = new LinkedList();
    for ( int i = 0; i < 10; i++ )
      c.add( "" + i );
    for ( Iterator it = c.iterator(); it.hasNext(); )
      System.out.println( it.next() );
  }
}

Besonders leicht - unter softwaretechnischen Gesichtspunkten - lässt sich die Datenstruktur ändern. Wir müssen nur

Collection c = new LinkedList();

etwa in

Collection c = new ArrayList();

ändern, und schon ist die Liste intern nicht mehr mit verketteten Elementen implementiert, sondern als Array. Es ist immer schön, wenn wir, etwa aus Gründen der Geschwindigkeit, so leicht die Datenstruktur ändern können. Der Rest des Programms bleibt unverändert.

Sich selbst in einer Liste haben

Die Implementierung der Listen-Klasse hat ein Problem, wenn ein Listen-Objekt sich selbst als Element enthält.

Die folgenden Zeilen provozieren einen StackOverflowError:

List l = new ArrayList();
l.add( "Hübsch" );
l.add( l );
System.out.println( l );   // hier ist das Problem

Das Phänomen tritt erst bei der Ausgabe mit println() auf, denn die Methode toString() auf der Liste l ruft wiederum toString() auf l auf, was wiederum toString() auf l aufruft und so weiter.


Galileo Computing

11.2.3 Schnittstellen, die Collection erweitern, und Mapdowntop

Es gibt einige elementare Schnittstellen, die einen Container weiter untergliedern, etwa in der Art, wie Elemente gespeichert werden.

Die Schnittstelle List

Die Schnittstelle List, die die Collection-Schnittstelle erweitert, enthält zusätzliche Operationen für eine geordnete Liste (auch Sequenz genannt) von Elementen. Seit Java 1.2 implementiert auch die Klasse Vector die Schnittstelle List. Auf die Elemente einer Liste lässt sich über einen ganzzahligen Index zugreifen, und es kann linear nach Elementen gesucht werden. Doppelte Elemente sind erlaubt. Da das AWT-Paket ebenfalls eine Klasse mit dem Namen »List« definiert, sollte bei Namenskonflikten der voll qualifizierte Name, also java.util.List oder java.awt.List verwendet werden. Neben Vector implementieren die Klassen AbstractList, LinkedList sowie ArrayList die List-Schnittstelle.

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

Die Schnittstelle Set

Ein Set ist eine im mathematischen Sinne definierte Menge von Objekten. Wie von mathematischen Mengen bekannt, darf ein Set keine doppelten Elemente enthalten. Für zwei nicht identische Elemente e1 und e2 eines Set-Objekts liefert der Vergleich e1.equals(e2) also immer false. Genauer gesagt: Aus e1.equals(e2) folgt, dass e1 und e2 identische Objektreferenzen sind, sich also auf dasselbe Mengenelement beziehen.

Besondere Beachtung muss Objekten geschenkt werden, die ihren Wert nachträglich ändern, da so zunächst ungleiche Mengenelemente inhaltlich gleich werden können. Dies kann ein Set nicht kontrollieren. Als weitere Einschränkung gilt, dass eine Menge sich selbst nicht als Element enthalten darf.

Zwei Klassen implementieren die Schnittstelle Set: die abstrakte Klasse AbstractSet und die konkrete Klasse HashSet.

Die Schnittstelle SortedSet

SortedSet erweitert Set um die Eigenschaft, Elemente sortiert auslesen zu können. Das Sortierkriterium wird durch ein Exemplar der Hilfsklasse Comparator bestimmt. TreeMap ist die einzige Klasse, die SortedMap implementiert.

Die Schnittstelle Map

Eine Klasse, die Map implementiert, realisiert einen assoziativen Speicher. Dieser verbindet einen Schlüssel mit einem Wert, etwa einen Namen mit einer E-Mail-Adresse. Im Gegensatz zu List ist eine Map unsortiert, und die Reihenfolge, in der die Elemente eingefügt werden, spielt keine Rolle. Ebenso wie Vector nun eine Implementierung von List ist, implementiert die Klasse Hashtable seit Java 1.2 die Schnittstelle Map.

Die Schnittstelle Map implementiert Collection nicht. Das liegt daran, dass nicht nur ein Element mit add() dem Container hinzugeführt wird, sondern zwei: Schlüssel und Wert. Darauf ist die allgemeine Collection nicht vorbereitet.

Die Schnittstelle SortedMap

Eine Map kann mit Hilfe eines Kriteriums sortiert werden und nennt sich dann SortedMap, es ist also eine Schnittstelle, die Map direkt erweitert. Das Sortierkriterium wird mittels eines Comparator-Objekts festgelegt. Damit kann auf einen assoziativen Speicher über einen Iterator in einer definierten Reihenfolge iteriert werden. Bisher implementiert nur die konkrete Klasse TreeMap die Schnittstelle SortedMap.


Galileo Computing

11.2.4 Konkrete Container-Klassentoptop

Werden wir nun ein wenig konkreter. Alle bisher vorgestellten Schnittstellen und Klassen dienen der Modellierung und dem Programmierer nur als Basis. Folgende Klassen sind konkrete Klassen und können von uns benutzt werden:


Listen ArrayList Implementiert Listen-Funktionalität wie ein Vector. Sie erweitert dabei die Klasse AbstractList. ArrayList implementiert die Schnittstelle List.
LinkedList LinkedList ist eine doppelt verkettete Liste, also eine Liste von Einträgen mit einer Referenz auf den jeweiligen Nachfolger und Vorgänger. Das ist nützlich beim Einfügen und Löschen von Elementen an beliebigen Stellen innerhalb der Liste. Diese Klasse erweitert AbstractSequentialList.
Mengen HashSet Eine Implementierung der Schnittstelle Set durch ein schnelles Hash-Verfahren
TreeSet Implementierung von Set durch einen Baum, der alle Elemente sortiert hält
Assoziativspeicher HashMap Implementiert einen assoziativen Speicher durch ein Hash-Verfahren. Sie erweitert die Klasse AbstractMap und ist damit auch eine Map.
TreeMap Exemplare dieser Klasse halten ihre Elemente in einem Binärbaum sortiert. TreeMap erweitert AbstractMap und implementiert SortedMap.





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