Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger. 
Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Objektorientierte Beziehungsfragen
7 Ausnahmen müssen sein
8 Äußere.innere Klassen
9 Besondere Typen der Java SE
10 Generics<T>
11 Lambda-Ausdrücke und funktionale Programmierung
12 Architektur, Design und angewandte Objektorientierung
13 Komponenten, JavaBeans und Module
14 Die Klassenbibliothek
15 Einführung in die nebenläufige Programmierung
16 Einführung in Datenstrukturen und Algorithmen
17 Einführung in grafische Oberflächen
18 Einführung in Dateien und Datenströme
19 Einführung ins Datenbankmanagement mit JDBC
20 Einführung in <XML>
21 Testen mit JUnit
22 Bits und Bytes und Mathematisches
23 Die Werkzeuge des JDK
A Java SE-Paketübersicht
Stichwortverzeichnis


Download:

- Beispielprogramme, ca. 35,4 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 16 Einführung in Datenstrukturen und Algorithmen
Pfeil 16.1 Listen
Pfeil 16.1.1 Erstes Listenbeispiel
Pfeil 16.1.2 Auswahlkriterium ArrayList oder LinkedList
Pfeil 16.1.3 Die Schnittstelle List
Pfeil 16.1.4 ArrayList
Pfeil 16.1.5 LinkedList
Pfeil 16.1.6 Der Feld-Adapter Arrays.asList(…)
Pfeil 16.1.7 toArray(…) von Collection verstehen – die Gefahr einer Falle erkennen
Pfeil 16.1.8 Primitive Elemente in Datenstrukturen verwalten
Pfeil 16.2 Mengen (Sets)
Pfeil 16.2.1 Ein erstes Mengenbeispiel
Pfeil 16.2.2 Methoden der Schnittstelle Set
Pfeil 16.2.3 HashSet
Pfeil 16.2.4 TreeSet – die sortierte Menge
Pfeil 16.2.5 Die Schnittstellen NavigableSet und SortedSet
Pfeil 16.2.6 LinkedHashSet
Pfeil 16.3 Assoziative Speicher
Pfeil 16.3.1 Die Klassen HashMap und TreeMap
Pfeil 16.3.2 Einfügen und Abfragen des Assoziativspeichers
Pfeil 16.3.3 Über die Bedeutung von equals(…) und hashCode() bei Elementen
Pfeil 16.3.4 Eigene Objekte hashen
Pfeil 16.3.5 LinkedHashMap und LRU-Implementierungen
Pfeil 16.3.6 IdentityHashMap
Pfeil 16.3.7 Das Problem veränderter Elemente
Pfeil 16.3.8 Aufzählungen und Ansichten des Assoziativspeichers
Pfeil 16.3.9 Die Properties-Klasse
Pfeil 16.4 Mit einem Iterator durch die Daten wandern
Pfeil 16.5 Algorithmen in Collections
Pfeil 16.5.1 Die Bedeutung von Ordnung mit Comparator und Comparable
Pfeil 16.5.2 Sortieren
Pfeil 16.5.3 Den größten und kleinsten Wert einer Collection finden
Pfeil 16.5.4 Nichtänderbare Datenstrukturen, immutable oder nur lesen?
Pfeil 16.5.5 Null Object Pattern und leere Sammlungen/Iteratoren zurückgeben
Pfeil 16.5.6 Echte typsichere Container
Pfeil 16.5.7 Mit der Halbierungssuche nach Elementen fahnden
Pfeil 16.6 Zum Weiterlesen
 

Zum Seitenanfang

16Einführung in Datenstrukturen und Algorithmen Zur vorigen ÜberschriftZur nächsten Überschrift

»Glück ist ganz einfach gute Gesundheit und ein schlechtes Gedächtnis.«

– Ernest Hemingway (1899–1961)

 

Zum Seitenanfang

16.1Listen Zur vorigen ÜberschriftZur nächsten Überschrift

Eine Liste steht für eine Sequenz von Daten, bei der die Elemente eine feste Reihenfolge besitzen. Die Schnittstelle java.util.List schreibt Verhalten vor, die alle konkreten Listen implementieren müssen. Interessante Realisierungen der List-Schnittstelle sind:

  • java.util.ArrayList: Liste auf der Basis eines Feldes

  • java.util.LinkedList: Liste durch verkettete Elemente

  • java.util.concurrent.CopyOnWriteArrayList: schnelle Liste, optimal für häufige nebenläufige Lesezugriffe

  • java.util.Vector: synchronisierte Liste seit Java 1.0, die der ArrayList wich. Die Klasse ist zwar nicht deprecated, sollte aber nicht mehr verwendet werden.

Die Methoden zum Zugriff über die gemeinsame Schnittstelle List sind immer die gleichen. So ermöglicht jede Liste einen Punktzugriff über get(index), und jede Liste kann alle gespeicherten Elemente sequenziell über einen Iterator hergeben. Doch die Realisierungen einer Liste unterscheiden sich in Eigenschaften wie der Performance, dem Speicherplatzbedarf oder der Möglichkeit der sicheren Nebenläufigkeit.

Da in allen Datenstrukturen jedes Exemplar einer von Object abgeleiteten Klasse Platz findet, sind die Listen grundsätzlich nicht auf bestimmte Datentypen fixiert, doch Generics spezifizieren diese Typen genauer.

 

Zum Seitenanfang

16.1.1Erstes Listenbeispiel Zur vorigen ÜberschriftZur nächsten Überschrift

Listen haben die wichtige Eigenschaft, dass sie sich die Reihenfolge der eingefügten Elemente merken und dass Elemente auch doppelt vorkommen können. Wir wollen diese Listenfähigkeit für ein kleines Gedächtnisspiel nutzen. Der Anwender gibt Städte für eine Route vor, die sich das Programm in einer Liste merkt. Nach der Eingabe eines neuen Ziels auf der Route soll der Anwender alle Städte in der richtigen Reihenfolge wiedergeben. Hat er das geschafft, kommt eine neue Stadt hinzu. Im Prinzip ist das Spiel unendlich, doch da sich kein Mensch unendlich viele Städte in der Reihenfolge merken kann, wird es zu einer Falscheingabe kommen, was das Programm beendet.

Listing 16.1com/tutego/insel/util/list/HowDoesYourRouteLooksLike.java

package com.tutego.insel.util.list;



import java.text.*;

import java.util.*;



public class HowDoesYourRouteLooksLike {

@SuppressWarnings( "resource" )

public static void main( String[] args ) {

List<String> cities = new ArrayList<>();



while ( true ) {

System.out.println( "Welche neue Stadt kommt hinzu?" );

String newCity = new Scanner( System.in ).nextLine();

cities.add( newCity );



System.out.printf( "Wie sieht die gesamte Route aus? (Tipp: %d %s)%n",

cities.size(),

cities.size() == 1 ? "Stadt" : "Städte" );



for ( String city : cities ) {

String guess = new Scanner( System.in ).nextLine();

if ( ! city.equalsIgnoreCase( guess ) ) {

System.out.printf( "%s ist nicht richtig, %s wäre korrekt. Schade!%n",

guess, city );

return;

}

}

System.out.println( "Prima, alle Städte in der richtigen Reihenfolge!" );

}

}

}
 

Zum Seitenanfang

16.1.2Auswahlkriterium ArrayList oder LinkedList Zur vorigen ÜberschriftZur nächsten Überschrift

Eine ArrayList (das Gleiche gilt für Vector) speichert Elemente in einem internen Array. LinkedList dagegen speichert die Elemente in einer verketteten Liste und realisiert die Verkettung mit einem eigenen Hilfsobjekt für jedes Listenelement. 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 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 einer ArrayList bedeutet dies viel Arbeit, es sei denn, das Element kann am Ende gelöscht oder – bei ausreichender Puffergröße – eingefügt werden. Soll ein Element nicht am Ende eingefügt oder gelöscht werden, müssen alle nachfolgenden Listenelemente verschoben werden.

  • Bei einer ArrayList kann die Größe des internen Feldes zu klein werden. Dann bleibt der Laufzeitumgebung nichts anderes übrig, als ein neues, größeres Feld-Objekt anzulegen und alle Elemente zu kopieren.

 

Zum Seitenanfang

16.1.3Die Schnittstelle List Zur vorigen ÜberschriftZur nächsten Überschrift

Die Schnittstelle List schreibt das allgemeine Verhalten für Listen vor. Die allermeisten Methoden kennen wir schon vom Collection-Interface, und zwar deshalb, weil List die Schnittstelle Collection erweitert. Hinzugekommen sind Methoden, die einen Bezug zur Position eines Elements haben – Mengen, die auch Collection implementieren, kennen keinen Index.

Hinzufügen und Setzen von Elementen

Die add(…)-Methode fügt neue Elemente an die Liste an, wobei eine Position die Einfügestellen bestimmen kann. Die Methode addAll(…) fügt fremde Elemente einer anderen Sammlung in die Liste ein. set(…) setzt ein Element an eine bestimmte Stelle, überschreibt das ursprüngliche Element und verschiebt es auch nicht wie add(…). Die Methode size() nennt die Anzahl der Elemente in der Datenstruktur.

Listing 16.2com/tutego/insel/util/list/ListDemo.java. main()

List<String> list1 = new ArrayList<>();

list1.add( "Eva" );

list1.add( 0, "Charisma" );

list1.add( "Pallas" );



List<String> list2 = Arrays.asList( "Tina", "Wilhelmine" );

list1.addAll( 3, list2 );

list1.add( "XXX" );

list1.set( 5, "Eva" );



System.out.println( list1 ); // [Charisma, Eva, Pallas, Tina, Wilhelmine, Eva]

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

Positionsanfragen und Suchen

Ob die Sammlung leer ist, bestimmt isEmtpy(). Ein Element an einer speziellen Stelle erfragen kann get(int). Ob Elemente Teil der Sammlung sind, beantworten contains(…) und containsAll(…). Wie bei Strings liefern indexOf(…) und lastIndexOf(…) die Fundpositionen.

boolean b = list1.contains( "Tina" );

System.out.println( b ); // true



b = list1.containsAll( Arrays.asList( "Tina", "Eva" ) );

System.out.println( b ); // true



Object o = list1.get( 1 );

System.out.println( o ); // Eva



int i = list1.indexOf( "Eva" );

System.out.println( i ); // 1



i = list1.lastIndexOf( "Eva" );

System.out.println( i ); // 5



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

Listen zu Feldern und neue Listen bilden

Von den Listen können Arrays abgeleitet werden, und es lassen sich Schnittmengen bilden:

String[] array = list1.toArray( new String[list1.size()] );

System.out.println( array[3] ); // "Tina"



List<String> list3 = new LinkedList<>( list1 );

System.out.println( list3 ); // [Charisma, Eva, Pallas, Tina,

// Wilhelmine, Eva]

list3.retainAll( Arrays.asList( "Tina", "Eva" ) );

System.out.println( list3 ); // [Eva, Tina, Eva]

Löschen von Elementen

Außerdem gibt es Methoden zum Löschen von Elementen. Hier bietet die Liste eine überladene remove(…)-Methode, removeIf(…) (seit Java 8) und removeAll(…). Den kürzesten Weg, alles aus der Liste zu löschen, bietet clear():

System.out.println( list1 ); // [Charisma, Eva, Pallas, Tina, Wilhelmine, Eva]

list1.remove( 1 );

System.out.println( list1 ); // [Charisma, Pallas, Tina, Wilhelmine, Eva]



list1.remove( "Wilhelmine" );

System.out.println( list1 ); // [Charisma, Pallas, Tina, Eva]



list1.removeAll( Arrays.asList( "Pallas", "Eva" ) );

System.out.println( list1 ); // [Charisma, Tina]



list1.clear();

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

[»]Hinweis

Die Methode remove(int) löscht ein Element an der gegebenen Stelle, remove(Object) sucht einmal nach einem equals-gleichen Objekt und löscht es dann, würde aber nicht weitersuchen nach anderen Vorkommen. removeIf(…) und removeAll(…) laufen immer komplett über die ganze Datenstruktur und schauen, ob ein Element dem Kriterium genügt, um es zu löschen, was mehrmals vorkommen kann.

[zB]Beispiel

Lösche alle null-Referenzen und Weißraum-Strings aus der Liste:

List<String> list = new ArrayList<>();

Collections.addAll( list, "1", "", " ", "zwei", null, "Polizei" );

list.removeIf( e -> Objects.isNull( e ) || e.trim().isEmpty() );

System.out.println( list ); // [1, zwei, Polizei]

Zusammenfassung

Die Methoden der Schnittstelle List (inklusive der aus der erweiterten Schnittstelle Collection) sind:

interface java.util.List<E>

extends Collection<E>
  • boolean add(E o)

    Fügt das Element am Ende der Liste an. Eine optionale Operation.

  • void add(int index, E element)

    Fügt ein Objekt an der angegebenen Stelle in die Liste ein. Eine optionale Operation.

  • boolean addAll(int index, Collection<? extends E> c)

    Fügt alle Elemente der Collection an der angegebenen Stelle in die Liste ein. Eine optionale Operation.

  • void clear()

    Löscht alle Elemente aus der Liste. Eine optionale Operation.

  • boolean contains(Object o)

    Liefert true, wenn das Element o in der Liste ist. Den Vergleich übernimmt equals(…), und es ist kein Referenzvergleich.

  • boolean containsAll(Collection<?> c)

    Liefert true, wenn alle Elemente der Sammlung c in der aktuellen Liste sind.

  • E get(int index)

    Wird das Element an dieser angegebenen Stelle der Liste liefern.

  • int indexOf(Object o)

    Liefert die Position des ersten Vorkommens für o oder –1, wenn kein Listenelement mit o inhaltlich – also per equals(…) und nicht per Referenz – übereinstimmt. Leider gibt es keine Methode, um ab einer bestimmten Stelle weiterzusuchen, so wie sie die Klasse String bietet. Dafür lässt sich jedoch eine Teilliste einsetzen, die subList(…) bildet – eine Methode, die später in der Aufzählung folgt.

  • boolean isEmpty()

    Liefert true, wenn die Liste leer ist.

  • Iterator<E> iterator()

    Liefert den Iterator. Die Methode ruft aber listIterator() auf und gibt ein ListIterator-Objekt zurück.

  • int lastIndexOf(Object o)

    Sucht von hinten in der Liste nach dem ersten Vorkommen von o und liefert –1, wenn kein Listenelement inhaltlich mit o übereinstimmt.

  • 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.

  • ListIterator<E> listIterator(int index)

    Liefert einen Listen-Iterator, der die Liste ab der Position index durchläuft.

  • E remove(int index)

    Entfernt das Element an der Position index aus der Liste.

  • boolean remove(Object o)

    Entfernt das erste Objekt in der Liste, das equals(…)-gleich mit o ist. Liefert true, wenn ein Element entfernt wurde. Eine optionale Operation.

  • boolean removeAll(Collection<?> c)

    Löscht in der eigenen Liste die Elemente aus c. Eine optionale Operation.

  • default boolean removeIf(Predicate<? super E> filter)

    Entfernt alle Elemente aus der Liste, bei denen das Prädikat erfüllt ist. Neu in Java 8.

  • boolean retainAll(Collection<?> c)

    Optional. Entfernt alle Objekte aus der Liste, die nicht in der Collection c vorkommen.

  • default void replaceAll(UnaryOperator<E> operator)

    Ruft auf jedem Element den Operator auf und schreibt das Ergebnis zurück. Neu in Java 8.

  • E set(int index, E element)

    Ersetzt das Element an der Stelle index durch element. Eine optionale Operation.

  • 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 Ansicht eines Ausschnitts der Originalliste dar. Änderungen an der Teilliste wirken sich auf die ganze Liste aus und umgekehrt (soweit sie den passenden Ausschnitt betreffen).

  • default void sort(Comparator<? super E> c)

    Sortiert die Liste, entspricht Collections.sort(dieseListe, c). Neu in Java 8.

  • boolean equals(Object o)

    Vergleicht die Liste mit einer anderen Liste. Zwei Listenobjekte sind gleich, wenn ihre Elemente paarweise gleich sind.

  • int hashCode()

    Liefert den Hashwert der Liste.

Was List der Collection hinzufügt, sind also die indexbasierten Methoden add(int index, E element), addAll(int index, Collection<? extends E> c), get(int index), indexOf(Object o), lastIndexOf(Object o), listIterator(), listIterator(int index), remove(int index), set(int index, E element) und subList(int fromIndex, int toIndex), zudem in Java 8 die beiden Default-Methoden replaceAll(…) und sort(…).

[»]Hinweis

Die remove(…)-Methode ist überschrieben und löscht a) einmal ein Element, das mit equals(…) gesucht wird, und b) mit remove(int) ein Element an der gegebenen Position. Irritierend ist Folgendes:

List<Integer> list = new ArrayList<>( Arrays.asList( 9, 8, 1, 7 ) );

Integer index = 1;

list.remove( index );

System.out.println( list ); // [9, 8, 7]

Es wird remove(Object) aufgerufen, weil Object dem Argumenttyp Integer am ähnlichsten ist. Somit verschwindet das Element Integer.valueOf(1) aus der Liste. Unboxing findet nicht statt. Das dürfte zu erwarten sein, und daher ist die Namensgebung index im Beispiel irreführend.

Nach Elementen suchen und ihre Position ausgeben

Ein zweites Beispiel zeigt die Möglichkeit von index(…) und subList(…), alle Positionen eines Elements in einer Liste zu finden. Die Codekompression gewinnt sicherlich keinen Preis. Arrays.asList(…) erzeugt aus einer Aufzählung eine Liste:

Listing 16.3com/tutego/insel/util/list/IndexOfSubList.java, main()

List<Integer> list = Arrays.asList( 1, 3, 4, 1 );

Integer o = 1;



for ( int i = list.indexOf( o ), j = –1;

i > j;

j = i, i += list.subList( i + 1, list.size() ).indexOf( o ) + 1 ) {

System.out.println( i );

}

Da nach 1 gesucht wurde, ist die Ausgabe 0 und 3.

Kopieren und Ausschneiden

Die Listen-Klassen implementieren clone() und erzeugen eine flache Kopie.

Um einen Bereich zu löschen, nutzen wir subList(from, to).clear(). Die subList-Technik deckt gleich noch einige andere Operationen ab, für die es keine speziellen Range-Varianten gibt, zum Beispiel indexOf(…), also die Suche in einem Teil der Liste.

[zB]Beispiel

Baue eine Liste auf, kürze sie, und gib die Elemente rückwärts aus:

List<String> list = new ArrayList<>(

Arrays.asList( "0 1 2 3 4 5 6 7 8 9".split( " " ) ) );

list.subList( 2, list.size() - 2 ).clear();

System.out.println( list ); // [0, 1, 8, 9]

for ( ListIterator<String> it = list.listIterator( list.size() );

it.hasPrevious(); )

System.out.print( it.previous() + " " ); // 9 8 1 0

subList(…) erzeugt wie viele andere Methoden der Collection-Datenstrukturen eine Ansicht auf die Liste, was bedeutet, dass Änderungen an dieser Teilliste zu Änderungen des Originals führen. Das gilt auch für clear(), was dazu genutzt werden kann, einen Teilbereich der Originalliste zu löschen.

[»]Hinweis

Die zum Löschen naheliegende Methode removeRange(int, int) kann nicht (direkt) eingesetzt werden, da sie protected[ 232 ](In AbstractList ist removeRange(int, int) gültig mit einem ListIterator implementiert, also nicht abstrakt. Die API-Dokumentation begründet das damit, dass removeRange(…) nicht zur offiziellen Schnittstelle von Listen gehört, sondern für die Autoren neuer Listenimplementierungen gedacht ist. ) ist. Das lässt sich zum Beispiel wie folgt beheben:

List<gewünschterTyp> list = new ArrayList<gewünschterTyp>() {

@Override public void removeRange( int fromIndex, int toIndex ) {

super.removeRange( fromIndex, toIndex );

}

};
 

Zum Seitenanfang

16.1.4ArrayList Zur vorigen ÜberschriftZur nächsten Überschrift

Jedes Exemplar der Klasse ArrayList vertritt ein Array mit variabler Länge. Der Zugriff auf die Elemente erfolgt effizient über Indizes, was ArrayList über die Implementierung der Markierungsschnittstelle RandomAccess andeutet.

Eine ArrayList erzeugen

Es existieren drei Konstruktoren, um ein ArrayList-Objekt zu erzeugen:

class java.util.ArrayList<E>

extends AbstractList<E>

implements List<E>, RandomAccess, Cloneable, Serializable
  • ArrayList()

    Eine leere Liste mit einer Anfangskapazität von zehn Elementen wird angelegt. Werden mehr als zehn Elemente eingefügt, muss sich die Liste vergrößern.

  • ArrayList(int initialCapacity)

    Eine Liste mit einem internen Platz von erst einmal initialCapacity Elementen wird angelegt.

  • ArrayList(Collection<? extends E> c)

    Kopiert alle Elemente der Collection c in das neue ArrayList-Objekt.

 

Zum Seitenanfang

16.1.5LinkedList Zur vorigen ÜberschriftZur nächsten Überschrift

Die Klasse LinkedList realisiert die Schnittstelle List als verkettete Liste und bildet die Elemente nicht auf ein Feld ab. Die Implementierung realisiert die LinkedList als doppelt verkettete Liste, in der jedes Element – die Ränder lassen wir außen vor – einen Vorgänger und Nachfolger hat. (Einfach verkettete Listen haben nur einen Nachfolger, was die Navigation in beide Richtungen schwierig macht.)

Klassendiagramm von LinkedList mit Vererbungsbeziehungen

Abbildung 16.1Klassendiagramm von LinkedList mit Vererbungsbeziehungen

Eine LinkedList hat neben den gegebenen Operationen aus der Schnittstelle List weitere Hilfsmethoden: Dabei handelt es sich um die Methoden addFirst(…), addLast(…), getFirst(), getLast(), removeFirst() und removeLast(). Die implementierten Schnittstellen Queue und Deque sind nicht ganz unschuldig an diesen neuen Methoden.

class java.util.LinkedList<E>

extends AbstractSequentialList<E>

implements List<E>, Deque<E>, Cloneable, Serializable
  • LinkedList()

    Erzeugt eine neue leere Liste.

  • LinkedList(Collection<? extends E> c)

    Kopiert alle Elemente der Collection c in die neue verkettete Liste.

 

Zum Seitenanfang

16.1.6Der Feld-Adapter Arrays.asList(…) Zur vorigen ÜberschriftZur nächsten Überschrift

Arrays von Objektreferenzen und dynamische Datenstrukturen passen nicht so richtig zusammen, obwohl sie schon häufiger zusammen benötigt werden. Die Java-Bibliothek bietet mit der statischen Methode Arrays.asList(…) an, ein existierendes Feld als java.util.List zu behandeln. Der Parametertyp ist ein Vararg – das ja intern auf ein Feld abgebildet wird –, sodass sich asList(…) auf zwei Arten verwenden lässt:

  • Arrays.asList(array): Die Variable array ist eine Referenz auf ein Feld, und das Ergebnis ist eine Liste, die die gleichen Elemente wie das Feld enthält.

  • Arrays.asList(e1, e2, e3): Die Elemente e1, e2, e3 sind Elemente der Liste.

Das Entwurfsmuster, das die Java-Bibliothek bei der statischen Methode anwendet, nennt sich Adapter. Es löst das Problem, die Schnittstellen eines Typs an eine andere Schnittstelle eines anderen Typs anzupassen.

[zB]Beispiel

Ermittle die Anzahl der ":-)"-Smileys im String.

String s = "Oten :-) Bilat :-) Iyot";

int i = Collections.frequency( Arrays.asList(s.split("\\s")), ":-)" );

System.out.println( i ); // 2

In String gibt es keine Methode zum Zählen von Teil-Strings.

[zB]Beispiel

Gib das größte Element eines Feldes aus:

Integer[] ints = { 3, 9, -1, 0 };

System.out.println( Collections.max( Arrays.asList( ints ) ) );

Zum Ermitteln des Maximums bietet die Utility-Klasse Arrays keine Methode, daher bietet sich die max(…)-Methode von Collections an. Auch etwa zum Ersetzen von Feldelementen bietet Arrays nichts, aber Collections. Sortieren und füllen kann Arrays aber schon, hier muss asList() nicht einspringen.

class java.util.Arrays
  • public static <T> List<T> asList(T... a)

    Ermöglicht es, mit der Schnittstelle einer Liste Zugriff auf ein Feld zu erhalten. Die variablen Argumente sind sehr praktisch.

[»]Hinweis

Wegen der Generics ist der Parameter-Typ von asList() ein Objektfeld, aber niemals ein primitives Feld. In unserem Beispiel von eben würde so etwas wie

int[] ints = { 3, 9, -1, 0 };

Arrays.asList( ints );

zwar compilieren, aber die Rückgabe von Arrays.asList(ints) ist vom Typ List<int[]>, was bedeutet, die gesamte Liste besteht aus genau einem Element, und dieses Element ist das primitive Feld. Zum Glück führt Collections.max(Arrays.asList(ints)) zu einem Compilerfehler, denn von einer List<int[]>, also einer Liste von Feldern, kann max(Collection<? extends T>) kein Maximum ermitteln. Anders wäre das bei Arrays.asList(3, 9, -1, 0), denn hier konvertiert der Compiler die Varargs-Argumente über Autoboxing schon direkt in Wrapper-Objekte, und es kommt eine Liste von Integer-Objekten heraus.

Internes

Die Rückgabe von asList(…) ist kein konkreter Klassentyp wie ArrayList oder LinkedList, sondern irgendetwas Unbekanntes, was asList(…) als List herausgibt. Diese Liste ist nur eine andere Ansicht des Feldes.

Jetzt gibt es allerdings einen Interpretationsspielraum, was genau mit der Rückgabe möglich ist. Zudem ist es nicht ganz uninteressant, zu wissen, ob die Liste einen schnellen Punktzugriff zulässt (RandomAccess implementiert) oder ob optionale Operationen wie Veränderungen oder sogar totale Reorganisationen denkbar sind. Ein Blick auf die Implementierung verrät mehr. Das Ergebnis ist ein Adapter, der Listenmethoden wie get(index) oder set(index, element) direkt auf das Feld umleitet. Da Feldlängen final sind, führen Modifikationsmethoden wie remove(…) oder add(…) zu einer UnsupportedOperationException.

[»]Hinweis

Sind Veränderungen an der asList(…)-Rückgabe erwünscht, so muss das Ergebnis in eine neue Datenstruktur kopiert werden, etwa so:

List<String> list = new ArrayList<>( Arrays.asList( "A", "B" ) );

list.add( "C" );
 

Zum Seitenanfang

16.1.7toArray(…) von Collection verstehen – die Gefahr einer Falle erkennen Zur vorigen ÜberschriftZur nächsten Überschrift

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.

[zB]Beispiel

Diese Anwendung von toArray() kopiert Punkte in ein Feld:

ArrayList<Point> list = new ArrayList<>();

list.add( new Point(13, 43) );

list.add( new Point(9, 4) );

Object[] points = list.toArray();

Wir erhalten nun ein Feld mit Referenzen auf Point-Objekte, können jedoch 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 in Point zu ändern; in dem Array befinden sich ja nur Referenzen auf Point-Exemplare:

Point[] points = list.toArray(); // inline Compilerfehler

Jetzt wird der Compiler einen Fehler melden, weil 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[]) list.toArray(); // inline Problem zur Laufzeit

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 durcheinandergebracht. Einem Array von Objektreferenzen 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, sich also 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(); // inline ArrayStoreException

/* 4 */ os[2] = "Trecker fahr'n"; // inline ArrayStoreException

Die Zeilen 3 und 4 sind vom Compiler erlaubt, führen aber zur Laufzeit zu einer ArrayStoreException.

Kommen wir wieder zur Methode toArray() zurück. Weil die auszulesende Datenstruktur alles Mögliche enthalten kann, muss der Typ der Elemente also 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 keinen allgemeineren (umfassenderen) Typ als Object 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 allgemeingültigen Implementierung von toArray() bleibt gar nichts anderes übrig, als das Array vom Typ Object[] und die Elemente vom Typ Object zu erzeugen.

Wenn sich auch die Elemente wieder in einen spezielleren Typ konvertieren lassen, ist das bei dem Array-Objekt selbst jedoch 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(T[])-Methode werfen. Sie akzeptiert als Parameter ein vorgefertigtes Array für das Ergebnis. Mit dieser Methode lässt sich erreichen, dass das Ergebnis-Array von einem spezielleren Typ als Object[] ist.

[zB]Beispiel

Wir fordern von der toArray()-Methode ein Feld vom Typ Point:

List<Point> list = new ArrayList<>();

list.add( new Point(13,43) );

list.add( new Point(9,4) );

Point[] points = (Point[]) list.toArray( new Point[0] );

Jetzt bekommen wir die Listenelemente in ein Array kopiert, und der Typ des Arrays ist Point[] – passend zu den aktuell vorhandenen Listenelementen. Der Parameter zeigt dabei den Wunschtyp an, der hier das Point-Feld ist.

Performance-Tipp

Am besten ist es, bei toArray(T[]) ein Feld anzugeben, das so groß ist wie das Ergebnisfeld, also so groß wie die Liste. Dann füllt nämlich toArray(T[]) genau dieses Feld und gibt es zurück, anstatt ein neues Feld aufzubauen:

ArrayList<Point> list = new ArrayList<>();

list.add( new Point(13,43) );

list.add( new Point(9,4) );

Point[] points = list.toArray( new Point[list.size()] );
 

Zum Seitenanfang

16.1.8Primitive Elemente in Datenstrukturen verwalten Zur vorigen ÜberschriftZur nächsten Überschrift

Jede Datenstruktur der Collection-API akzeptiert, auch wenn sie generisch verwendet wird, nur Objekte. Primitive Datentypen nehmen die Sammlungen nicht auf, was zur Konsequenz hat, dass Wrapper-Objekte nötig sind (über das Boxing fügt Java scheinbar primitive Elemente ein, doch in Wahrheit sind es Wrapper-Objekte). Auch wenn es also

List<Double> list = new ArrayList<>();

list.add( 1.1 );

list.add( 2.2 );

heißt, sind es zwei neue Double-Objekte, die aufgebaut werden und in die Liste wandern. Anders und klarer geschrieben, sehen wir hier, was wirklich passiert:

List<Double> list = new ArrayList<>();

list.add( Double.valueOf(1.1) );

list.add( new Double(2.2) );

Dem Aufruf von Double.valueOf(…) ist der new-Operator nicht abzulesen, doch die Methode ist implementiert als: Double valueOf(double d){ return new Double(d); }.

Spezialbibliotheken

Für performante Anwendungen und große Mengen von primitiven Elementen ist es sinnvoll, eine Klasse für den speziellen Datentyp einzusetzen. Anstatt so etwas selbst zu programmieren, kann der Entwickler auf drei Implementierungen zurückgreifen:

 


Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück

 

 


Copyright © Rheinwerk Verlag GmbH 2017

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.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de