Galileo Computing < openbook >
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel (2. Aufl.) von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Java ist auch eine Insel (2. Auflage)
gp Kapitel 11 Datenstrukturen und Algorithmen
  gp 11.1 Mit einem Iterator durch die Daten wandern
  gp 11.2 Dynamische Datenstrukturen
  gp 11.3 Die Klasse Vector
    gp 11.3.1 Vektoren erzeugen
    gp 11.3.2 Funktionen
    gp 11.3.3 Arbeitsweise des internen Arrays
    gp 11.3.4 Die Größe eines Felds
    gp 11.3.5 Eine Aufzählung und gleichzeitiges Verändern
  gp 11.4 Stack, der Stapel
    gp 11.4.1 Die Methoden von Stack
    gp 11.4.2 Ein Stack ist ein Vektor – aha!
  gp 11.5 Die Klasse Hashtable und assoziative Speicher
    gp 11.5.1 Ein Objekt der Klasse Hashtable erzeugen
    gp 11.5.2 Einfügen und Abfragen der Datenstruktur
    gp 11.5.3 Die Arbeitsweise einer Hashtabelle
    gp 11.5.4 Aufzählen der Elemente
    gp 11.5.5 Ausgabe der Hashtabelle und Gleichheitstest
    gp 11.5.6 Klonen
  gp 11.6 Die abstrakte Klasse Dictionary
    gp 11.6.1 Zugriff und Abfrage
    gp 11.6.2 Metainformationen
    gp 11.6.3 Iterationen über die Elemente
  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.8 Windows-typische INI-Dateien
  gp 11.9 Queue, die Schlange
  gp 11.10 Die Collection-API
    gp 11.10.1 Die Schnittstelle Collection
    gp 11.10.2 Schnittstellen, die Collection erweitern, und Map
    gp 11.10.3 Abstrakte Basisklassen für Container
    gp 11.10.4 Konkrete Container-Klassen
    gp 11.10.5 Unterschiede zu den älteren Datenstrukturen und die Synchronisation
    gp 11.10.6 Das erste Programm mit Container-Klassen
    gp 11.10.7 Iteratoren
    gp 11.10.8 Der Comparator
    gp 11.10.9 toArray() von Collection verstehen – Chance für eine Falle erkennen
  gp 11.11 Listen
    gp 11.11.1 AbstractList
    gp 11.11.2 Optionale Methoden
    gp 11.11.3 ArrayList
    gp 11.11.4 LinkedList
  gp 11.12 Algorithmen
    gp 11.12.1 Datenmanipulation
    gp 11.12.2 Größten und kleinsten Wert einer Collection finden
    gp 11.12.3 Sortieren
    gp 11.12.4 Elemente in der Collection suchen
  gp 11.13 Typsichere Datenstrukturen
  gp 11.14 Die Klasse BitSet für Bitmengen
    gp 11.14.1 Ein BitSet anlegen und füllen
    gp 11.14.2 Mengenorientierte Operationen
    gp 11.14.3 Funktionsübersicht
    gp 11.14.4 Primzahlen in einem BitSet verwalten
  gp 11.15 Ein Design-Pattern durch Beobachten von Änderungen
    gp 11.15.1 Design-Pattern
    gp 11.15.2 Das Beobachter-Pattern (Observer/Observable)


Galileo Computing

11.3 Die Klasse Vector  downtop

Jedes Exemplar der Klasse Vector vertritt ein Array mit variabler Länge. Der Zugriff auf die Elemente erfolgt über Indizes, zwar nicht über den Operator [], aber doch über Methoden, die einen Index als Parameter annehmen. Da in einem Vector jedes Exemplar einer von Object abgeleiteten Klasse Platz findet, ist ein Vector nicht auf bestimmte Datentypen fixiert. Dies ist ein Problem, denn beim Zugriff auf Elemente des Vektors muss der Entwickler wissen, welchen Typ die Vektorelemente haben werden. Dasselbe Problem haben wir bei anderen Datenstrukturen auch.


Galileo Computing

11.3.1 Vektoren erzeugen  downtop

Um ein Vector-Objekt zu erzeugen, existieren vier Konstruktoren.

class java.util.Vector
extends AbstractList implements List, Cloneable, Serializable

gp  Vector()
Ein leerer Vektor mit einer Anfangskapazität von zehn Elementen wird angelegt.
gp  Vector( int initialCapacity )
Ein leerer Vektor wird angelegt, der Platz für zunächst initialCapacity Elemente bietet, ohne dass der Vektor vergrößert werden muss.
gp  Vector( int initialCapacity, int capacityIncrement )
Ein Vektor mit Platz für zunächst initialCapacity Elemente wird angelegt. Zusätzlich beschreibt die Zahl capacityIncrement, um wie viele Elemente die Kapazität des Vektors jedes Mal erhöht wird, wenn der verfügbare Platz erschöpft ist.
gp  Vector( Collection c )
Nimmt ein Objekt vom Typ Collection als Parameter entgegen Wir werden uns später näher mit der Schnittstelle Collection auseinandersetzen.
Beispiel   Erstelle einen Vector v und fülle die Datenstruktur mit einer Zeichenkette und einer Ganzzahl.
Vector v = new Vector();
v.addElement( "ICE 924 Hildegard von Bingen" );
v.addElement( new Integer(23) );

Die eingefügte Zahl durch das Integer-Objekt macht deutlich, dass als Elemente nur Objekte akzeptiert werden und keine primitiven Datentypen. Eine Unterklasse könnte jedoch Vector erweitern und die Daten für den Benutzer unsichtbar in Objekten kapseln. Für performante Anwendungen ist es jedoch sinnvoll, eine komplette eigene Klasse für den speziellen Datentyp einzusetzen. So etwas muss nicht mehr selbst programmiert werden – die Lösung ist etwas GNU Trove: High performance collections for Java, unter http://trove4j.sourceforge.net/.

Abbildung


Galileo Computing

11.3.2 Funktionen  downtop

Die Klasse Vector bietet eine Vielzahl von Methoden, die wir hier zusammenfassen:

Class java.util.Vector
Extends AbstractList Implements List, Cloneable, Serializable

gp  boolean add( Object o )
Fügt das Objekt o dem Vektor hinter dem letzten hinzugefügten Element an. Reicht der interne Puffer nicht aus, wird gegebenenfalls der Vektor vergrößert. Der Rückgabewert ist immer true.
gp  void add( int index, Object o )
Fügt das Objekt o dem Vektor an der gewünschten Position index hinzu. Ist kein Platz an der Stelle index, wird eine ArrayIndexOutOfBoundsException ausgelöst.
gp  void addElement( Object o )
Wie add(o).
gp  boolean addAll( Collection c )
Fügt alle Elemente der Collection ein. Die Rückgabe ist true, wenn Elemente von c in den Vektor aufgenommen wurden.
gp  boolean addAll( int index, Collection c )
Fügt alle Elemente der Collection ab index ein. Die alten Elemente werden verschoben. Bei einem falschen Index wird eine ArrayIndexOutOfBoundsException ausgelöst. Die Rückgabe ist true, wenn es Veränderungen am Vektor gab.
gp  int capacity()
Liefert die aktuelle Kapazität des Vektors. capacity() ist in der Praxis eher unwichtig, denn das Kapazitätsmanagement soll die Klasse selber erzeugen.
gp  Object clone()
Die Klasse implementiert die clone()-Methode von Object, das heißt, eine Referenz auf eine Kopie des aufrufenden Vektors wird zurückgegeben. Es handelt sich um eine flache Kopie; Original-Vektor und Kopie referenzieren also dieselben Element-Objekte.
gp  boolean contains( Object o )
Sucht das Element. Liefert true zurück, wenn ein mit o inhaltlich gleiches Objekt im Vektor enthalten ist.
gp  boolean containsAll( Collection c )
Sind alle Elemente der Collection im Vektor enthalten?
gp  void copyInto( Object array[] )
Kopiert die Elemente des Vektors in das Array array.
gp  Object elementAt( int index )
Das an der Stelle index befindliche Vektorelement wird zurückgegeben.
gp  Enumeration elements()
Gibt ein Aufzählungs-Objekt zurück. Somit können wir die Elemente eines Vektors aufzählen.
gp  Object firstElement()
Gibt das erste Element des Vektors zurück.
gp  int indexOf( Object o )
Sucht im Vektor nach dem Objekt o. Liefert den Index des passenden Vektorelements zurück oder -1, falls kein gleiches Element vorhanden ist.
gp  int indexOf( Object o, int index )
Sucht im Vektor ab der Position index nach dem Objekt o. Liefert den Index des passenden Vektorelements zurück oder -1, falls kein gleiches Element vorhanden ist.
gp  void insertElementAt( Object o, int index )
Fügt Objekt o an der Stelle index in den Vektor ein und verschiebt die anderen Elemente um eine Position nach hinten. Auch das ursprünglich an der Position index befindliche Element wird verschoben.
gp  boolean isEmpty()
Gibt true zurück, falls der Vektor kein Element enthält.
gp  Object lastElement()
Gibt das letzte Element des Vektors (das mit dem größten Index) zurück.
gp  int lastIndexOf( Object o )
Sucht rückwärts durch den Vektor nach o und gibt die Position des passenden Elements zurück. Existiert kein mit o gleiches Element im Vektor, wird -1 zurückgegeben.
gp  int lastIndexOf( Object o, int index )
Sucht rückwärts ab Position index durch den Vektor nach o und gibt die Position des passenden Elements zurück. Existiert kein mit o gleiches Element im Vektor, wird -1 zurückgegeben.
gp  boolean remove( Object o )
Entfernt das erste mit o übereinstimmende Element aus dem Vektor. Das Funktionsergebnis signalisiert, ob ein passendes Element gefunden (und gelöscht) wurde.
gp  Object remove( int index )
Entfernt das Element an der Stelle index. Alle nachfolgenden Elemente rücken um eine Position vor. Das gelöschte Elemente wird zurückgegeben.
gp  boolean removeAll( Collection c )
Entfernt alle Elemente in der Collection aus dem Vektor. Liefert true, wenn Elemente gelöscht wurden.
gp  void removeAllElements()
Entfernt alle Elemente aus dem Vektor.
gp  boolean removeElement( Object o )
Wie remove(o).
gp  void removeElementAt( int index )
Wie remove(index).
gp  boolean retainAll( Collection c )
Entfernt alle Elemente aus dem Vektor, die nicht in der Collection enthalten sind. Liefert true, wenn sich der Vektor aufgrund der Löschoperationen geändert hat.
gp  Object set( int index, Object o )
Das Objekt o wird an der Stelle index im Vektor platziert. Das vorher an dieser Position befindliche Element wird aus dem Vektor entfernt, aber die Referenz als Rückgabewert geliefert.
gp  void setElementAt( Object o, int index )
Wie set(index, o), nur die Parameter sind vertauscht.
gp  void setSize( int size )
Setzt die Größe des Vektors. Überzählige Elemente werden aus dem Vektor entfernt beziehungsweise Null-Referenzen als neue Einträge angehängt.
gp  int size()
Gibt die Anzahl der Elemente im Vektor zurück. Es gilt stets: v.size() <= v.capacity().
gp  int subList( int fromList, int toList )
Liefert eine Liste des Vektors von fromList bis toList.
gp  Object[] toArray()
Erzeugt ein neues Feld und kopiert alle Elemente des Vektors in das Feld.
gp  Object[] toArray( Object a[] )
Erzeugt ein neues Feld vom Typ a und kopiert alle Elemente des Vektors in das Feld. Falls das übergebene Feld genauso groß wie der Vektor ist, werden die Elemente in a kopiert. Ist a größer als der Vektor, werden die verbleibenden Stellen mit null gefüllt. Ist das Feld a zu klein, wird ein neues Feld vom Typ a angelegt und die Elemente des Vektors werden in das neue Feld kopiert.
gp  String toString()
Konvertiert den Vektor in einen String; eine textuelle Aufzählung seiner Elemente.
gp  void trimToSize()
Übersteigt die Kapazität eines Vektors die Anzahl der enthaltenen Elemente, so beseitigt diese Methode die überzählige Kapazitätsreserve. Dadurch wird der Speicherbedarf minimiert.

Galileo Computing

11.3.3 Arbeitsweise des internen Arrays  downtop

Ein Vektor muss zwei Größen verwalten: Zum einen die Anzahl der gespeicherten Elemente nach außen, zum anderen die interne Größe des Felds. Ist die Kapazität des Felds größer als die Anzahl der Elemente, so können noch Elemente aufgenommen werden, ohne dass der Vektor etwas unternehmen muss. Die Anzahl der Elemente im Vektor, die Größe, liefert die Methode size(), und die Kapazität des darunter liegenden Arrays liefert capacity().

Der Vector vergrößert sich automatisch, falls mehr Elemente aufgenommen werden als ursprünglich am Platz vorgesehen waren. Diese Operation heißt Resizing. Dabei spielen die Größen initialCapacity und capacityIncrement besonders für ein effizientes Arbeiten eine wichtige Rolle. Sie sollten passend gewählt sein. Betrachten wir daher zunächst die Funktionsweise des Vektors, falls das interne Array zu klein ist.

Wenn das Array zehn Elemente fasst, nun aber ein elftes eingefügt werden soll, so muss das Laufzeitsystem einen neuen Speicherbereich reservieren und jedes Element des alten Felds in das neue kopieren. Dies kostet Zeit. Schon aus diesem Grund sollte der Konstruktor Vector(int initialCapacity) gewählt werden, da dieser eine Initialgröße festsetzt. Das Wissen über unsere Daten hilft dann der Datenstruktur. Falls kein Wert voreingestellt wurde, so werden zehn Elemente angenommen. In vielen Fällen ist dieser Wert zu klein.

Nun haben wir zwar darüber gesprochen, dass ein neues Feld angelegt wird und die Elemente kopiert werden, haben aber nichts über die Größe des neuen Felds gesagt. Hier gilt die »Verdopplungsmethode«. Wird der Vector vergrößert, so ist das neue Feld doppelt so groß wie das alte. Dies ist eine Vorgehensweise, die für kleine und schnell wachsende Felder eine clevere Lösung darstellt, für große Felder aber schnell zum Verhängnis werden kann. Im Fall, dass wir die Vergrößerung selbst bestimmen wollen, nutzen wir den Konstruktor Vector(int initialCapacity, int capacityIncrement) oder ändern die Größe über Methoden.


Galileo Computing

11.3.4 Die Größe eines Felds  downtop

Mit capacity() gelangen wir an die interne Größe des Arrays. Sie kann mit ensureCapacity() geändert werden. Ein Aufruf von ensureCapacity(int minimumCapacity) bewirkt beim Vektor, dass er insgesamt mindestens minimumCapacity Elemente aufnehmen kann, ohne dass ein Resizing nötig wird. Ist die aktuelle Kapazität des Vektors kleiner als minimumCapacity, so wird mehr Speicher angefordert. Der Vektor verkleinert die aktuelle Kapazität nicht , falls sie schon höher als minimumCapacity ist. Um aber auch diese Größe zu ändern, und somit ein nicht mehr wachsendes Vektor-Array so groß wie nötig zu machen, gibt es, ähnlich wie beim String mit Leerzeichen, die Methode trimToSize(). Sie reduziert die Kapazität des Vektors auf die Anzahl der Elemente, die gerade im Vector sind. Mit size() lässt sich die Anzahl der Elemente im Vektor erfragen. Sie gibt die wirkliche Anzahl der Elemente zurück. Wollen wir diese ändern, so nutzen wir dazu setSize(int newSize). Ist die neue Größe kleiner als die alte, werden die Elemente am Ende des Vektors abgeschnitten. Ist newSize größer als die alte Größe, werden die neu angelegten Elemente mit null initialisiert. Vorsicht ist bei newSize=0 geboten, denn setSize(0) bewirkt das Gleiche wie removeAllElements().

Die Vector-Klasse bietet uns für die Konvertierung von Vector-Objekten in Felder eine Methode an.

class java.util.Vector
extends AbstractList implements List, Cloneable, Serializable

gp  void copyInto( Object obArray[] )
Kopiert die Elemente des Vektors in das Array. Falls das bereitgestellte Objektfeld nicht so groß ist wie der Vektor, tritt eine ArrayIndexOutOfBoundsException auf.

Der folgende Programmausschnitt erzeugt ein Array für Objektreferenzen und kopiert Referenzen für alle Elemente eines Vektors hinein:

Object obArray[] = new Object[myVector.size()];
myVector.copyInto( obArray );

Galileo Computing

11.3.5 Eine Aufzählung und gleichzeitiges Verändern  toptop

Noch vor einem weiteren Fehler bei der Aufzählung von Elementen eines Vectors muss gewarnt werden. Da dieser Fehler allerdings in der Version 1.2 behoben wurde, ist nachfolgendes Beispiel nur von historischer Bedeutung. Das folgende Programm fügt einem Vector drei Integer hinzu. Nach dem Hinzufügen besorgen wir uns eine Aufzählung, geben das erste Element aus und löschen es dann. Anschließend geben wir das nächste Element aus.

Listing 11.3   VectorEnumerator.java

import java.util.*;
public class VectorEnumerator
{
  public static void main( String args[] )
  {
    Vector temp = new Vector();
    int i = 0;

    temp.addElement( new Integer(i++) );
    temp.addElement( new Integer(i++) );
    temp.addElement( new Integer(i++) );

    Enumeration e = temp.elements();
    System.out.println( e.nextElement() );
    temp.removeElementAt( 0 );
    System.out.println( e.nextElement() );
  }
}

Die Programmausführung ergibt folgende Ausgabe:

0
2

Exemplarisch sehen wir, wie problematisch es ist, wenn in einer existierenden Aufzählung die Datenstruktur geändert wird. In diesem Fall werden beim Löschen des i-ten Elements die Elemente ab der Position i+1 bis zur Größe des Vektors um eine Stelle verschoben. Damit haben wir aber das Problem, dass jede Aufzählung auf dem Vektor das aktuelle Argument überspringt. In dem oberen Beispiel fehlt uns das Element 1. Mit den Möglichkeiten der Aufzählung unter Verwendung der Schnittstelle Enumeration ist das Problem auch unter 1.2 nicht behoben.

Mit dem Iterator-Interface, das unter 1.2 im Collection-Framework eingeführt worden ist, werden diese Probleme umgangen. Ohne dieses neue Iterator-Interface bleibt uns nichts anderes übrig, als sehr aufmerksam den Vektor zu füllen oder eine eigene Implementierung des Vektors vorzunehmen, der die Löschoperationen sichert, bevor die Aufzählung durch den Vektor vollständig ist.

Iteratoren mit der remove()-Methode

Enumeration und Iterator verbieten beide Modifikationen an der zugrunde liegenden Datenstruktur während über die Elemente iteriert wird. Die Iteratoren für die Collection-Klassen aus JDK 1.2 erkennen diesen Verstoß allerdings mit sehr hoher Wahrscheinlichkeit und lösen einen entsprechenden Laufzeitfehler aus. Enumerations verhalten sich einfach nur stillschweigend undefiniert, so wie oben im Beispiel gezeigt. Die obige Konstruktion mit removeElementAt() löst auch mit einem Iterator nur eine Exception aus. Umgangen wird das Problem nur, wenn die remove()-Methode des Iterators verwendet wird. Falls gleichzeitig mehrere Iteratoren über denselben Vektor laufen, gibt es aber immer noch Ausnahmen von anderen Iteratoren, außer von dem, dessen remove()-Methode aufgerufen wurde.






1   Zudem können null-Referenzen ganz normal als Elemente eines Vektors auftreten, bei den anderen Datenstrukturen gibt es Einschränkungen





Copyright © Galileo Press GmbH 2003
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