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.11 Listen  downtop

Die Schnittstelle List, die in den Klassen ArrayList und LinkedList implementiert ist, erlaubt sequenziellen Zugriff auf die gespeicherten Elemente. Beide 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.

ArrayList speichert Elemente in einem Array (so wie es Vector macht). LinkedList dagegen speichert die Elemente in einer verketteten Liste und realisiert die Verkettung mit einem eigenen Hilfsobjekt für jedes Listenelement. Somit 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 nur einfach 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 Listenelemente 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.11.1 AbstractList  downtop

Da AbstractList viele wichtige Methoden für die beiden Listen-Klassen vorgibt, beginnen wir hier mit der Beschreibung. Dies erspart uns doppeltes Aufzählen bei den speziellen Unterklassen. 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
extends AbstractCollection implements List

gp  void add( int index, Object element )
Optional. Fügt ein Objekt an der angegebenen Stelle in die Liste ein.
gp  boolean add( Object o )
Optional. Fügt das Element am Ende der Liste an.
gp  boolean addAll( int index, Collection 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 Object get( int index )
Wird das Element an dieser angegebenen Stelle der Liste liefern.
gp  int hashCode()
Liefert den Hashcode der Liste.
gp  int indexOf( Object o )
Liefert die Position des ersten Vorkommens für o oder -1, wenn kein Listenelement 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 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 Listenelement inhaltlich mit o übereinstimmt.
gp  ListIterator 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 listIterator( int index )
Liefert einen Listen-Iterator, der die Liste ab der Position index durchläuft.
gp  Object 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  Object set( int index, Object element )
Optional. Ersetzt das Element an der Stelle index durch element.
gp  List 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 (soweit sie den passenden Ausschnitt betreffen).

Galileo Computing

11.11.2 Optionale Methoden  downtop

Rufen wir eine optionale Methode auf, die von der Unterklasse nicht implementiert wird, erhalten wir eine UnsupportedOperationException. Die vielen Optional-Anmerkungen erschrecken zunächst und lassen die Klassen beziehungsweise Schnittstellen irgendwie unzuverlässig oder nutzlos wirken. Die konkreten Standard-Implementierungen der Collection-API bieten diese Operationen jedoch vollständig an; nur die Spezial-Wrapper für nur Lese-Container lassen sie weg. Das Konzept der optionalen Operationen ist umstritten, wenn Methoden zur Laufzeit eine Exception auslösen. Besser wären natürlich separate kleinere Schnittstellen, die nur die Leseoperationen enthalten und zur Übersetzungszeit überprüft werden können; doch dann würde es noch deutlich mehr Schnittstellen im Util-Paket geben.

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.12   AbstractListDemo.java

import java.util.*;

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

    // Hinzufügen

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

    List ta = new ArrayList();
    ta.add( "I" );

    a.add( ta );
    a.addAll( 3, ta );
    a.add( "a" );

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


    // Abfrage- und Suchfunktionen

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

    b = a.containsAll( ta );
    System.out.println( b );  // true

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

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

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

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

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

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


    // Teilfelder bilden

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

    ta = new ArrayList();     // Menge mit a bilden
    ta.add( "a" );

    List tb = new ArrayList();
    tb.addAll( a );           // tb enthält Elemente aus a

    tb.retainAll( ta );       // Schnitt bilden
    System.out.println( tb ); // [a, a]

    // Iteratoren besorgen

    ListIterator it = a.listIterator();

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

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

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

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


    // Löschen und verändern

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

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

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

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

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

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

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

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

Galileo Computing

11.11.3 ArrayList  downtop

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.13   ArrayListDemo.java

import java.util.*;

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

    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.11.4 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   In AbstractList ist removeRange() gültig mit einem ListIterator implementiert.





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