Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
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.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 Generische Datentypen in der Collection-API
    gp 11.2.4 Der Iterator, die Schnittstelle Iterable und das erweiterte for
    gp 11.2.5 Schnittstellen, die Collection erweitern, und Map
    gp 11.2.6 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.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 Queues (Schlangen)
    gp 11.5.1 Blockierende Queues und Prioritätswarteschlangen
  gp 11.6 Assoziative Speicher und die Klasse HashMap
    gp 11.6.1 Ein Objekt der Klasse HashMap erzeugen
    gp 11.6.2 Einfügen und Abfragen der Datenstruktur
    gp 11.6.3 Wichtige Eigenschaften von Assoziativspeichern
    gp 11.6.4 Elemente im Assoziativspeicher müssen unveränderbar bleiben
    gp 11.6.5 Die Arbeitsweise einer Hash-Tabelle
    gp 11.6.6 Aufzählen der Elemente
    gp 11.6.7 Der Gleichheitstest und der Hash-Wert einer Hash-Tabelle
    gp 11.6.8 Klonen
  gp 11.7 Die Properties-Klasse
    gp 11.7.1 Properties setzen und lesen
    gp 11.7.2 Properties verketten
    gp 11.7.3 Eigenschaften ausgeben
    gp 11.7.4 Hierarchische Eigenschaften
    gp 11.7.5 Properties speichern
    gp 11.7.6 Über die Beziehung Properties und Hashtable
  gp 11.8 Mengen (Sets)
    gp 11.8.1 HashSet
    gp 11.8.2 TreeSet – die Menge durch Bäume
  gp 11.9 Algorithmen in Collections
    gp 11.9.1 Datenmanipulation: Umdrehen, Füllen, Kopieren
    gp 11.9.2 Vergleichen von Objekten mit Comparator und Comparable
    gp 11.9.3 Größten und kleinsten Wert einer Collection finden
    gp 11.9.4 Sortieren
    gp 11.9.5 nCopies()
    gp 11.9.6 Singletons
    gp 11.9.7 Elemente in der Collection suchen
  gp 11.10 Synchronisation der 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.3 Listen  downtop

Die Klassen ArrayList, LinkedList und Vector implementieren die Schnittstelle List und erlauben sequenziellen Zugriff auf die gespeicherten Elemente. Die genannten Klassen leiten sich von der abstrakten Klasse AbstractList ab, die schon grundlegende Funktionalität für Listen liefert. Eine Liste gibt über iterator() einen speziellen ListEnumerator zurück. LinkedList besitzt die zusätzliche Oberklasse AbstractSequentialList.

Speicherung als Feld oder verkettete Liste

ArrayList speichert Elemente in einem Array (so wie es auch Vector macht). LinkedList dagegen speichert die Elemente in einer verketteten Liste und realisiert die Verkettung mit einem eigenen Hilfsobjekt für jedes Listen-Element. Es ergeben sich Einsatzgebiete, die einmal für LinkedList und einmal für ArrayList sprechen. Da ArrayList intern ein Array benutzt, ist der Zugriff auf ein spezielles Element über die Position in der Liste sehr schnell. Eine LinkedList muss aufwändiger durchsucht werden, und dies kostet seine Zeit. Die verkettete Liste ist aber deutlich im Vorteil, wenn Elemente mitten in der Liste gelöscht oder eingefügt werden; hier muss einfach nur die Verkettung der Hilfsobjekte an einer Stelle verändert werden. Bei einem ArrayList-Objekt als Container bedeutet dies viel Arbeit, es sei denn, das Element muss am Ende gelöscht oder eingefügt werden. Zum einen müssen alle nachfolgenden Listen-Elemente beim Einfügen und Löschen im Array verschoben werden, zum anderen kann beim Einfügen das verwendete Array-Objekt zu klein werden. Dann bleibt, wie beim Vector, nichts anderes übrig, als ein neues Array-Objekt anzulegen und alle Elemente zu kopieren.


Galileo Computing

11.3.1 AbstractList  downtop

Da AbstractList viele wichtige Methoden für die beiden Listen-Klassen vorgibt, beginnen wir hier mit der Beschreibung. Dies erspart uns ein doppeltes Aufzählen bei den Unterklassen ArrayList und LinkedList. Einige Methoden kennen wir schon vom Collection-Interface, und zwar deshalb, da AbstractCollection die Schnittstelle Collection implementiert und AbstractList die Methoden aus AbstractCollection erbt.



abstract class java.util.  AbstractList<E>  
extends AbstractCollection<E>
implements List<E>

gp  boolean add( E o )
Optional. Fügt das Element am Ende der Liste an.
gp  void add( int index, E element )
Optional. Fügt ein Objekt an der angegebenen Stelle in die Liste ein.
gp  boolean addAll( int index, Collection<? extends E> c )
Optional. Fügt alle Elemente der Collection an der angegebenen Stelle in die Liste ein.
gp  void clear()
Optional. Löscht alle Elemente aus der Liste.
gp  boolean equals( Object o )
Vergleicht die Liste mit dem Objekt. Zwei Listen-Objekte sind gleich, wenn ihre Elemente paarweise gleich sind.
gp  abstract E get( int index )
Wird das Element an dieser angegebenen Stelle der Liste liefern.
gp  int indexOf( Object o )
Liefert die Position des ersten Vorkommens für o oder -1, wenn kein Listen-Element mit o inhaltlich übereinstimmt. Leider gibt es hier keine Methode, um ab einer bestimmten Stelle weiterzusuchen, so wie sie die Klasse String bietet. Dann lässt sich jedoch eine Teilliste einsetzen.
gp  Iterator<E> iterator()
Liefert den Iterator. Überschreibt die Methode in AbstractCollection, obwohl wir auch listIterator() für die spezielle Liste haben. Die Methode ruft aber listIterator() auf und gibt ein ListIterator-Objekt zurück.
gp  int lastIndexOf( Object o )
Sucht von hinten in der Liste nach dem ersten Vorkommen von o, liefert -1, wenn kein Listen-Element inhaltlich mit o übereinstimmt.
gp  ListIterator<E> listIterator()
Liefert einen Listen-Iterator für die ganze Liste. Ein Listen-Iterator bietet gegenüber dem allgemeinen Iterator für Container zusätzliche Operationen.
gp  ListIterator<E> listIterator( int index )
Liefert einen Listen-Iterator, der die Liste ab der Position index durchläuft.
gp  E remove( int index )
Entfernt das Element an Position index aus der Liste.
gp  protected void removeRange( int fromIndex, int toIndex )
Löscht den Teil der Liste von Position fromIndex bis toIndex. Das Element an der Stelle fromIndex wird mit gelöscht und das bei toIndex nicht. Da diese Methode protected ist, lässt sie sich nur in Unterklassen benutzen. Der Autor der Unterklasse kann dann entscheiden, ob er die Methode mit einer öffentlichen Version überschreibt oder diese Operation unter einem anderen Methodennamen öffentlich anbieten will.
gp  E set( int index, E element )
Optional. Ersetzt das Element an der Stelle index durch element.
gp  List<E> subList( int fromIndex, int toIndex )
Liefert den Ausschnitt dieser Liste von Position fromIndex (einschließlich) bis toIndex (nicht mit dabei). Die zurückgelieferte Liste stellt eine Sicht auf einen Ausschnitt der Originalliste dar. Änderungen an der Teilliste wirken sich auf die ganze Liste aus und umgekehrt (so weit sie den passenden Ausschnitt betreffen).
gp  int hashCode()
Liefert den Hashcode der Liste.

ListIterator

ListIterator ist eine Erweiterung von Iterator. Diese Schnittstelle fügt noch Methoden hinzu, damit an der aktuellen Stelle auch Elemente eingefügt werden können. Mit einem ListIterator lässt sich rückwärts laufen und auf das vorhergehende Element zugreifen.



public interface   ListIterator<E>  
extends Iterator

gp  boolean hasPrevious(), boolean hasNext(),
Liefert true, wenn es ein vorhergehendes/nachfolgendes Element gibt.
gp  E previous(), E next(),
Liefert das vorangehende/nächste Element der Liste und NoSuchElementException, wenn es das Element nicht gibt.
gp  int previousIndex(), int nextIndex()
Liefert den Index des vorhergehenden/nachfolgenden Elements. Geht previousIndex() vor die Liste, so liefert die Funktion -1, geht nextIndex() hinter die Liste, liefert die Funktion die Länge der gesamten Liste.
gp  void remove()
Optional. Entfernt das letzte von next() oder previous() zurückgegebene Element.
gp  void add( E o )
Optional. Fügt ein neues Objekt in die Liste ein.
gp  void set( E o )
Optional. Ersetzt das von next() oder previous() gegebene Objekt.

Galileo Computing

11.3.2 Beispiel mit List-Methoden  downtop

Das nachfolgende Programm zeigt die wichtigsten Methoden und wie sie auf einem Exemplar einer konkreten Unterklasse zu AbstractList, wie ArrayList oder LinkedList, aussehen.

Listing 11.2   AbstractListDemo.java


import java.util.*;

public class AbstractListDemo
{
  public static void main( String args[] )
{
    //    List a = new LinkedList();
    List<Object> list1 = new ArrayList<Object>();

    // Hinzufügen

    list1.add( "b" );
    list1.add( 0, "a" );
    list1.add( "i" );

    List<String> list2 = new ArrayList<String>();
    list2.add( "I" );

    list1.add( list2 );
    list1.addAll( 3, list2 );
    list1.add( "a" );

    System.out.println( list1 );        // [a, b, i, I, [I], a]


   // Abfrage-und Suchfunktionen

    boolean b = list1.contains( "a" );
    System.out.println( b );            // true

    b = list1.containsAll( list2 );
    System.out.println( b );            // true

    list2.add( "I2" );
    b = list1.containsAll( list2 );
    System.out.println( b );            // false
    System.out.println( list1 );        // [a, b, i, I, [I, I2], a]

    Object o = list1.get( 1 );
    System.out.println( o );            // b

    int i = list1.indexOf( "i" );
    System.out.println( i );            // 2

    i = list1.lastIndexOf( "a" );
    System.out.println( i );            // 5

    b = list1.isEmpty();
    System.out.println( b );            // false

    b = new ArrayList().isEmpty();
    System.out.println( b );            // true


    // Teilfelder bilden

    Object array[] = list1.toArray();
    System.out.println( array[3] );     // "I"

    list2 = new ArrayList<String>();    // Menge mit a bilden
    list2.add( "a" );

    List<Object> list3 = new ArrayList<Object>();
    list3.addAll( list1 );              // tb enthält Elemente aus a

    list3.retainAll( list2 );           // Schnitt bilden
    System.out.println( list3 );        // [a, a]

    // Iteratoren besorgen

    ListIterator<Object> it = list1.listIterator();

    it.add( "s" );                      // Vorne ersetzen
    System.out.println( list1 );        // [s, a, b, i, I, [I, I2], a]

    it.next();
    it.remove();
    System.out.println( list1 );        // [s, b, i, [I, I2], a]

    it.next();
    it.set( "i" );
    System.out.println( list1 );        // [s, i, i, I, [I, I2], a]

    it = list1.listIterator( list1.size()/2 - 1 );
    it.add( "l" );
    it.add( "v" );
    System.out.println( list1 );        // [s, i, l, v, i, I, [I, I2], a]


    // Löschen und verändern

    it = list1.listIterator( list1.size() );

    it.previous();                      // Liste rückwärts
    it.remove();
    System.out.println( list1 );        // [s, i, l, v, i, I, [I, I2]]

    list1.remove( 6 );
    System.out.println( list1 );        // [s, i, l, v, i, I]

    list1.remove( "v" );
    System.out.println( list1 );        // [s, i, l, i, I]

    list1.set( 1, new ArrayList() );
    System.out.println( list1 );        // [s, [], l, i, I]

    System.out.println( list1.size() ); // 5

    list1.clear();
    System.out.println( list1 );        // []

    System.out.println( list1.size() ); // 0
  }
}

Galileo Computing

11.3.3 ArrayList  downtop

Jedes Exemplar der Klasse ArrayList vertritt ein Array mit variabler Länge. Der Zugriff auf die Elemente erfolgt über Indizes. Da in einer ArrayList jedes Exemplar einer von Object abgeleiteten Klasse Platz findet, ist eine ArrayList nicht auf bestimmte Datentypen fixiert. Über Generics lassen sich diese Typen genauer spezifizieren.

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

Eine ArrayList erzeugen

Um ein ArrayList-Objekt zu erzeugen, existieren drei Konstruktoren.



class java.util.  ArrayList<E>  
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

gp  ArrayList()
Eine leere Liste mit einer Anfangskapazität von zehn Elementen wird angelegt. Werden mehr als zehn Elemente eingefügt, muss die Liste sich vergrößern.
gp  ArrayList( int initialCapacity )
Eine Liste mit initialCapacity freien Elementen wird angelegt.
gp  ArrayList( Collection<? extends E> c )
Kopiert alle Elemente der Collection c in das neue ArrayList-Objekt.

Beispiel   Erstelle einen List l und fülle die Datenstruktur mit einer Zeichenkette und einer Ganzzahl

List v = new ArrayList();
v.  add  ( "ICE 924 Hildegard von Bingen" );
v.  add  ( new Integer(23) );

Primitive Elemente in den Collection-Datenstrukturen

Die eingefügte Zahl durch das Integer-Objekt macht deutlich, dass als Elemente nur Objekte akzeptiert werden und keine primitiven Datentypen. Über das Boxing fügt jedoch Java 5 automatisch primitive Elemente ein, doch diese sind dann immer noch Wrapper-Objekte.

Für performante Anwendungen ist es 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/.

Kopieren, Ausschneiden und Einfügen

Eine wichtige Methode, die ArrayList von AbstractList überschreibt, ist clone(). Damit lässt sich leicht eine Kopie der Liste erzeugen. Es entsteht so allerdings nur eine flache Kopie der Datenstruktur. Ein besonderer Witz ist removeRange(int, int). Die Methode wird zwar überschrieben, sie ist aber immer noch protected. Die API-Dokumentation beschreibt dazu, dass removeRange() nicht zur offiziellen Schnittstelle von Listen gehört, sondern für die Autoren neuer Listenimplementierungen gedacht ist. Beispielsweise kann removeRange() für ArrayList-Objekte deutlich effizienter implementiert werden als in der Klasse AbstractList. Das offizielle Idiom für die Funktionalität von removeRange() ist subList(from, to).clear(). Die subList()-Technik erschlägt gleich noch ein paar andere Operationen, für die es keine speziellen Range-Varianten gibt, zum Beispiel indexOf(), also die Suche in einem Teil der Liste.

Listing 11.3   ArrayListDemo.java


import java.util.*;

public class ArrayListDemo
{
  public static void main( String args[] )
  {
    ArrayList<String> a = new ArrayList<String>();

    for ( int i = 1; i <= 20; i++ )
      a.add( "" + i );

    a.subList( a.size()/2-3, a.size()/2+3 ).clear();

    System.out.println( a );

    // Iterator besorgen und Reihe ausgeben

    ListIterator it = a.listIterator( a.size() );

    while( it.hasPrevious() )
      System.out.print( it.previous() + " " );

    System.out.println();

    // Datenstruktur kürzen und klonen

    a.subList( 4, a.size() ).clear();

    System.out.println( a );
  }
}

Zusätzlich sehen wir hier noch einen ganz normalen ListIterator im Einsatz. Dann ist die Ausgabe Folgende:


[1, 2, 3, 4, 5, 6, 7, 14, 15, 16, 17, 18, 19, 20]
20 19 18 17 16 15 14 7 6 5 4 3 2 1
[1, 2, 3, 4]

Galileo Computing

11.3.4 asList() und die »echten« Listen  downtop

Arrays von Objektreferenzen und dynamische Datenstrukturen passen nicht so richtig zusammen. Die Java-Bibliothek bietet mit der Methode asList() der Hilfsklasse Arrays an, die Felder als Listen anzusprechen.

Bei den uns bekannten Unterklassen ArrayList und LinkedList stellt sich die Frage, ob asList() damit etwas zu tun hat. Die Antwort ist nein. Arrays implementiert eine eigene interne Listen-Klasse, die mit ArrayList oder LinkedList von der Implementierung her nichts gemeinsam hat, bis auf die Tatsache, dass sie die Schnittstelle List implementiert, obwohl die interne Klasse auch ArrayList heißt. Die Unterscheidung ist wichtig, da ArrayList aus Arrays eine Mini-Implementierung des Vorbilds ist. Daher lässt sich mit ihr auch nicht alles machen, was spätestens dann klar wird, wenn über einen Iterator Elemente in der Liste gelöscht werden sollen. Dann folgt eine UnsupportedOperationException(), die aus AbstractList kommt. Die kleine ArrayList-Implementierung in Arrays erweitert nämlich AbstractList und implementiert nur das Notwendigste. Damit wir jetzt dennoch Elemente löschen können, müssen wir die einfache Liste in eine spezielle LinkedList oder ArrayList umbauen. Das sieht dann wie folgt aus:


List l = new ArrayList( Arrays.asList( new String[]{"Purzelchen", "Hässchen"} ) );

Iterator i = l.iterator();
i.next();
i.remove();

Galileo Computing

11.3.5 toArray() von Collection verstehen – die Gefahr einer Falle erkennen  downtop

Die toArray()-Methode aus der Schnittstelle Collection gibt laut Definition ein Array von Objekten zurück. Es ist wichtig zu verstehen, welchen Typ die Einträge und das Array selbst haben. Eine Implementierung der Collection-Schnittstelle ist ArrayList.


Beispiel   Eine Anwendung von toArray(), die Punkte in ein Feld kopiert

ArrayList<Point> l = new ArrayList<Point>();
l.add( new Point(13,43) );
l.add( new Point(9,4) );
Object points[] = l.  toArray();  

Wir erhalten nun ein Feld mit Referenzen auf Point-Objekte. Doch wir können zum Beispiel nicht einfach points[1].x schreiben, um auf das Attribut des Point-Exemplars zuzugreifen, denn das Array points hat den deklarierten Elementtyp Object. Es fehlt die explizite Typumwandlung, und erst ((Point)points[1]).x ist korrekt. Doch spontan kommen wir sicherlich auf die Idee, einfach den Typ des Arrays auf Point zu ändern. In dem Array befinden sich ja nur Referenzen auf Point-Exemplare.


Point points[] = l.toArray();             // Vorsicht!

Jetzt wird der Compiler einen Fehler melden, da der Rückgabewert von toArray() ein Object[] ist. Spontan reparieren wir dies, indem wir eine Typumwandlung auf ein Point-Array an die rechte Seite setzen.


Point points[] = (Point[]) l.toArray();   // Gefährlich!

Jetzt haben wir zur Übersetzungszeit kein Problem mehr, aber zur Laufzeit wird es immer knallen, auch wenn sich im Array tatsächlich nur Point-Objekte befinden.

Diesen Programmierfehler müssen wir verstehen. Was wir falsch gemacht haben, ist einfach: Wir haben den Typ des Arrays mit den Typen der Array-Elemente durcheinander gebracht. Einem Array von Objekt-Referenzen können wir alles zuweisen.


Object os[] = new Object[3];
os[0] = new Point();
os[1] = "Trecker fahr’n";
os[2] = new Date();

Wir merken, dass der Typ des Arrays Object[] ist, und die Array-Elemente sind ebenfalls vom Typ Object. Hinter dem new-Operator, der das Array-Objekt erzeugt, steht der gemeinsame Obertyp für zulässige Array-Elemente. Bei Object[]-Arrays dürfen die Elemente Referenzen für beliebige Objekte sein. Klar ist, dass ein Array nur Objektreferenzen aufnehmen kann, die mit dem Typ für das Array selbst kompatibel sind, also sich auf Exemplare der angegebenen Klasse beziehen oder auf Exemplare von Unterklassen dieser Klasse.


/* 1 */  Object os[] = new Point[3];
/* 2 */  os[0] = new Point();
/* 3 */  os[1] = new Date();           // !!
/* 4 */  os[2] = "Trecker fahr’n";     // !!

Zeile 3 und 4 sind vom Compiler erlaubt, führen aber zur Laufzeit zu einer java.lang.ArrayStoreException.

Kommen wir wieder zurück zur Methode toArray(). Da die auszulesende Datenstruktur alles Mögliche enthalten kann, muss also der Typ der Elemente Object sein. Wir haben gerade festgestellt, dass der Elementtyp des Array-Objekts, das die Methode toArray() als Ergebnis liefert, mindestens so umfassend sein muss. Da es einen allgemeineren (umfassenderen) Typ als Object nicht gibt, ist auch der Typ des Arrays Object[]. Dies muss so sein, auch wenn die Elemente einer Datenstruktur im Einzelfall einen spezielleren Typ haben. Einer allgemein gültigen Implementierung von toArray() bleibt gar nichts anderes übrig, als das Array vom Typ Object[] und die Elemente vom Typ Object zu erzeugen.


public Object[] toArray() {
  Object[] objs = new Object[size()];
  Iterator it = iterator();
  for (int i = 0; i < objs.length; i++) {
    objs[i] = it.next();
  }
  return objs;
}

Wenn sich auch die Elemente wieder auf einen spezielleren Typ konvertieren lassen, ist das jedoch bei dem Array-Objekt selbst nicht der Fall. Ein Array-Objekt mit Elementen vom Typ X ist nicht automatisch auch selbst vom Typ X[], sondern von einem Typ Y[], wobei Y eine (echte) Oberklasse von X ist.

Die Lösung für das Problem

Bevor wir nun eine Schleife mit einer Typumwandlung für jedes einzelne Array-Element schreiben oder eine Typumwandlung bei jedem Zugriff auf die Elemente vornehmen, sollten wir einen Blick auf die zweite toArray()-Funktion werfen. Sie akzeptiert als Parameter ein vorgefertigtes Array für das Ergebnis. Mit dieser Funktion lässt sich erreichen, dass das Ergebnis-Array von einem spezielleren Typ als Object[] ist.


Beispiel   Wir fordern von der toArray()-Funktion ein Feld vom Typ Point.

ArrayList<Point> l = new ArrayList<Point>();
l.add( new Point(13,43));
l.add( new Point(9,4));
Point points[] = (Point[]) l.  toArray( new Point[0]);  

Jetzt bekommen wir die Listen-Elemente in ein Array kopiert, und der Typ des Arrays ist, passend zu den aktuell vorhandenen Listen-Elementen, Point[].

Spannend ist die Frage, wie so etwas funktionieren kann. Dazu verwendet die Methode toArray(Object[]) die Technik Reflection, um dynamisch ein Array vom gleichen Typ wie das übergebene Array zu erzeugen. Wollten wir ein Array b vom Typ des Arrays a mit Platz für len Elemente anlegen, so schreiben wir


Object b[] = (Object[]) Array.newInstance (a.getClass().getComponentType(), len );

Mit a.getClass().getComponentType() erhalten wir ein Class-Objekt für den Elementtyp des Arrays, zum Beispiel das Class-Objekt Point.class für die Klasse Point. a.getClass() allein liefert ein Class-Objekt für das Array a, etwa ein Objekt, das den Typ Point[] repräsentiert. Array.newInstance(), eine statische Methode von java.lang.reflect.Array, konstruiert ein neues Array mit dem Elementtyp aus dem Class-Objekt und der angegebenen Länge. Nichts anderes macht auch ein new X[len], nur dass hier der Elementtyp zur Übersetzungszeit festgelegt werden muss. Da der Rückgabewert von newInstance() ein allgemeines Object ist, muss letztendlich noch die Konvertierung in ein passendes Array stattfinden.

Ist das übergebene Array so groß, dass es alle Elemente der Collection aufnehmen kann, kopiert toArray() die Elemente aus der Collection in das Feld. Oft wird aber dort ein new X[0] anzeigen, dass wir ein neu erzeugtes Array-Objekt wünschen. Im Übrigen entspricht natürlich toArray(new Object[0]) dem Aufruf von toArray(). Dennoch gibt die Java-Bibliothek zwei völlig getrennte Implementierungen an, da toArray()einfacher und effizienter zu implementieren ist.


Galileo Computing

11.3.6 Die interne Arbeitsweise von ArrayList und Vector  downtop

Eine ArrayList beziehungsweise Vector 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 die Liste etwas unternehmen muss. Die Anzahl der Elemente in der Liste, die Größe, liefert die Methode size(), und die Kapazität des darunter liegenden Arrays liefert capacity().

Die Liste vergrößert sich automatisch, falls mehr Elemente aufgenommen werden als ursprünglich am Platz vorgesehen waren. Diese Operation heißt Resizing. Dabei spielt die Größe initialCapacity für effizientes Arbeiten eine wichtige Rolle. Sie sollte passend gewählt sein. Betrachten wir daher zunächst die Funktionsweise der Liste, 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 ArrayList(int initialCapacity)/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 gibt es Strategien wie die »Verdopplungsmethode« beim Vector. Wird er 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. Für den Fall, dass wir die Vergrößerung selbst bestimmen wollen, nutzen wir den Konstruktor Vector(int initialCapacity, int capacityIncrement), der die Verdopplung ausschaltet und eine fixe Vergrößerung befiehlt. Die ArrayList verdoppelt nicht, sie nimmt die neue Größe mal 1,5. Bei ihr gibt es leider auch den capacityIncrement im Konstruktor nicht.

Die Größe eines Felds

Die interne Größe des Arrays kann mit ensureCapacity() geändert werden. Ein Aufruf von ensureCapacity(int minimumCapacity) bewirkt, dass die Liste insgesamt mindestens minimumCapacity Elemente aufnehmen kann, ohne dass ein Resizing nötig wird. Ist die aktuelle Kapazität der Liste 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 in der Liste sind. Mit size() lässt sich die Anzahl der Elemente in der Liste erfragen. Sie gibt die wirkliche Anzahl der Elemente zurück.

Bei der Klasse Vector lässt sich mit setSize(int newSize) auch die Größe der Liste verändern. 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().


Galileo Computing

11.3.7 LinkedList  toptop

Eine verkettete Liste hat neben den normalen Funktionen aus AbstractList noch weitere Hilfsmethoden, um leicht einen Stack oder eine Schlange zu programmieren. Es handelt sich dabei um die Funktionen addFirst(), addLast(), getFirst(), getLast(), removeFirst() und removeLast().






1   AbstractList ist removeRange() gültig mit einem ListIterator implementiert.

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





Copyright © 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