Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 5. Auflage
 <<    <     >    >>   API  Kapitel 15 - Collections II

15.8 Typisierte Klassen und generische Collections



15.8.1 Grundlagen

Dieser Abschnitt beschreibt eine Erweiterung, die seit der J2SE 5.0 zur Verfügung steht und unter dem Namen »Generics« bekannt geworden ist. Es geht dabei vordergründig um die Möglichkeit, typsichere Collection-Klassen zu definieren. Also solche, in die nicht nur allgemein Objekte des Typs Object gesteckt werden können, sondern die durch vorhergehende Typisierung sicherstellen, dass nur Objekte des korrekten Typs (etwa Integer oder String) eingefügt werden können. Diese, von vielen Java-Entwicklern seit langer Zeit geforderte, Spracherweiterung bringt zwei wichtige Vorteile:

  • Da die Einfügeoperationen typsicher sind, kann zum Zugriffszeitpunkt kein fehlerhaft typisiertes Objekt mehr in der Collection sein. Bereits der Compiler stellt sicher, dass nur Objekte eingefügt werden können, die zur Collection passen. Ausnahmen des Typs ClassCastException, die beim späteren Auslesen von fehlerhaft typisierten Collection-Elementen auftreten würden (also erst zur Laufzeit des Programms), gehören der Vergangenheit an.
  • Da der Compiler den Typ der Collection kennt, kann die umständliche Typkonvertierung beim Auslesen der Collection-Elemente entfallen, und der Programmcode wird kürzer und lesbarer.
 JDK1.1-6.0 

Genau genommen geht es nicht nur um Collections im eigentlichen Sinne, sondern um die Typisierung von beliebigen Java-Klassen. Also die Möglichkeit, festzulegen, dass eine bestimmte Klasse X zwar so implementiert wurde, dass sie prinzipiell mit allen anderen Klassen zusammen arbeitet (bzw. Objekte deren Typs aufnimmt), im konkreten Anwendungsfall von X aber die Möglichkeit besteht, die Zusammenarbeit (etwa aus Sicherheits- oder Konsistenzgründen) auf eine fest vorgegebene andere Klasse zu beschränken.

 Hinweis 

Was sich etwas kompliziert anhört, wollen wir durch ein einfaches Beispiel illustrieren:

001 public static void printSorted1(int... args)
002 {
003   Vector v = new Vector();
004   for (int i = 0; i < args.length; ++i) {
005     v.addElement(new Integer(args[i]));
006   }
007   Collections.sort(v);
008   for (int i = 0; i < v.size(); ++i) {
009     int wert = 10 * ((Integer)v.elementAt(i)).intValue();
010     System.out.print(wert + " ");
011   }
012   System.out.println();
013 }
Listing 15.8: Eine untypisierte Sortiermethode

printSorted1 bekommt als Parameter eine Menge von Ganzzahlen übergeben und hat die Aufgabe, diese mit 10 zu multiplizieren und sortiert auf der Konsole auszugeben. Die Methode legt dazu einen Vector v an und fügt in diesen zunächst die in Integer konvertierten int-Werte ein. Anschließend sortiert sie den Vector, liest die Integer-Objekte aus, konvertiert sie in int-Werte zurück und gibt die mit 10 multiplizierten Ergebnisse auf der Konsole aus.

Seit der J2SE 5.0 kann die Methode nun typsicher gemacht werden:

001 public static void printSorted2(int... args)
002 {
003   Vector<Integer> v = new Vector<Integer>();
004   for (int i = 0; i < args.length; ++i) {
005     v.addElement(new Integer(args[i]));
006   }
007   Collections.sort(v);
008   for (int i = 0; i < v.size(); ++i) {
009     int wert = 10 * v.elementAt(i).intValue();
010     System.out.print(wert + " ");
011   }
012   System.out.println();
013 }
Listing 15.9: Die typsichere Version der Sortiermethode

Der Vector wurde hier mit einem Typ-Parameter versehen, der in spitzen Klammern angegeben wird:

Vector<Integer> v = new Vector<Integer>();

Dadurch wird dem Compiler mitgeteilt, dass dieser Vector ausschließlich Integer-Objekte aufnehmen kann. Alle Versuche, darin einen String, ein Double oder irgendein anderes Nicht-Integer-Objekt zu speichern, werden vom Compiler unterbunden. Auch der zweite der oben genannten Vorteile kommt zum Tragen: beim Zugriff auf Vector-Elemente mit Hilfe der Methode elementAt werden diese automatisch in ein Integer konvertiert, der übliche Typecast kann also entfallen.

Auf diese Weise können nun seit der J2SE 5.0 alle Collection-Klassen typsicher verwendet werden: einfach den Datentyp in spitzen Klammern direkt hinter dem Klassennamen angeben! Auch bei Collections, die mit mehr als einem Parameter arbeiten, ist das möglich, also inbesondere bei den verschiedenen Maps. Hier werden beide Parameter in spitzen Klammern angegeben und durch Kommata voneinander getrennt. Wir werden dazu später ein Beispiel sehen.

Zunächst soll jedoch das obige Beispiel weiter vereinfacht werden. Tatsächlich ist printSorted2 nämlich etwas länger als printSorted1, d.h. wir haben uns die Typsicherheit durch zusätzlichen Code erkauft. Daß es wesentlich einfacher geht, zeigt folgende Variante:

001 public static void printSorted3(int... args)
002 {
003   Vector<Integer> v = new Vector<Integer>();
004   for (int i : args) {
005     v.addElement(i); 
006   }
007   Collections.sort(v);
008   for (Integer i : v) {
009     System.out.print((i * 10) + " "); 
010   }
011   System.out.println();
012 }
Listing 15.10: Die vereinfachte Version der typsicheren Variante

Hier kommen zusätzlich folgende Techniken zum Einsatz:

Dieses Programm sieht wesentlich besser aus als die erste Fassung. Es ist nun sowohl typsicher als auch besser lesbar. Möglich gemacht wird dies durch verschiedene Neuerungen der J2SE 5.0, die hier im Zusammenspiel ihr Synergiepotential entfalten. Autoboxing und Autounboxing werden in Abschnitt 10.2.3 erläutert und die erweiterte for-Schleife in Abschnitt 6.3.3. Auch die variablen Parameterlisten sind eine Neuerung der J2SE 5.0; sie werden in Abschnitt 7.3.4 erläutert.

15.8.2 Collections mit mehreren Typparametern

Wie bereits erwähnt können auch Collections typsicher gemacht werden, deren Methoden üblicherweise mehr als einen Parameter erwarten. Ein gutes Beispiel dafür ist das Interface Map und dessen implementierende Klassen (etwa HashMap, TreeMap oder Hashtable). Sie speichern nicht einzelne Werte, sondern Schlüssel-Wert-Paare. Soll eine solche Klasse typsicher verwendet werden, sind bei der Deklaration zwei Typ-Parameter anzugeben:

Hashtable<String, Integer> h = new Hashtable<String, Integer>();

An die Einfügeoperationen, die beide Parameter erwarten, muss nach einer solchen Deklaration zwangsweise ein String und ein Integer übergeben werden. Die Zugriffsmethoden dagegen erwarten einen String als Schlüssel und liefern einen Integer als Rückgabewert. Beispielhaft wollen wir uns eine Methode ansehen, die eine Liste von Strings erwartet und dann zählt, wie oft jedes einzelne Wort darin vorkommt. Eine Pre-5.0-Implementierung könnte so aussehen:

001 public static void wordCount1(String[] args)
002 {
003   Hashtable h = new Hashtable();
004   for (int i = 0; i < args.length; ++i) {
005     int cnt = 1;
006     if (h.containsKey(args[i])) {
007       cnt = 1 + ((Integer)h.get(args[i])).intValue();
008     }
009     h.put(args[i], new Integer(cnt));
010   }
011   System.out.println(h);
012 }
Listing 15.11: Ein untypisierter Wortzähler

Für jedes Element des Parameter-Arrays wird geprüft, ob es schon in der Hashtable h enthalten ist. Ist das der Fall, wird das Wort als Schlüssel verwendet, der zugehörige Zählerstand aus h gelesen und um 1 erhöht. Ist das nicht der Fall, wird der Zähler mit 1 initialisiert. Anschließend wird der Zählerwert mit dem Wort als Schlüssel in die Hashtable geschrieben.

Seit der J2SE 5.0 kann man die Methode stark vereinfachen:

001 public static void wordCount2(String... args)
002 {
003   Hashtable<String, Integer> h = new Hashtable<String, Integer>();
004   for (String key : args) {
005     if (h.containsKey(key)) {
006       h.put(key, 1 + h.get(key));
007     } else {
008       h.put(key, 1);
009     }
010   }
011   System.out.println(h);
012 }
Listing 15.12: Die 5.0-Wortzählervariante

Auch hier machen wir uns gleich alle drei oben genannten Erweiterungen der J2SE 5.0 zu Nutze. Zudem gibt es einen weiteren Vorteil. Da nun die Datentypen der Methoden put und get bekannt sind, können wir - dank der Verkürzung durch Autoboxing und Autounboxing - die Programmstruktur übersichtlicher machen. Wir schreiben dazu die put- und get-Operationen in eine Zeile, die Hilfsvariable cnt wird gar nicht mehr gebraucht.

15.8.3 Eine eigene typisierte Listenklasse

Nachdem wir uns in den vorherigen Abschnitten angesehen haben, wie generische Collections verwendet werden, wollen wir nun eine eigene generische Listenklasse implementieren. Deren Interface soll bewußt schlank gehalten werden, um unnötige Verkomplizierungen zu vermeiden. Es besteht aus je einer Methode, um Elemente einzufügen und auszulesen, einer Methode zur Abfrage der Größe und aus einem Iterator, um die Elemente (u.a. mit den neuen foreach-Schleifen der J2SE 5.0) durchlaufen zu können.

Der Einfachheit halber wollen wir die Liste mit einem Array als interne Datenstruktur realisieren und definieren dazu folgende Klasse:

001 import java.util.*;
002 
003 /**
004  * Die folgende Klasse realisiert eine einfache Liste mit einer
005  * festen Größe. Die Liste kann typisiert werden, so dass
006  * Zugriffs- und Hinzufügemethoden typsicher werden. Darüber
007  * hinaus implementiert sie das Interface Iterable und stellt
008  * einen typsicheren Iterator zur Verfügung, um die Verwendung
009  * in J2SE-5.0-foreach-Schleifen zu ermöglichen.
010  */
011 public class MiniListe<E>
012 implements Iterable<E>
013 {
014   private Object[] data;
015   private int      size;
016 
017   /**
018    * Erzeugt eine leere Liste, die maximal maxSize Elemente
019    * aufnehmen kann.
020    */
021   public MiniListe(int maxSize)
022   {
023     this.data = new Object[maxSize];
024     this.size = 0;
025   }
026 
027   /**
028    * Fügt ein Element zur Liste hinzu. Falls diese schon
029    * voll ist, wird eine Exception ausgelöst.
030    */
031   public void addElement(E element)
032   {
033     if (size >= data.length) {
034       throw new ArrayIndexOutOfBoundsException();
035     }
036     data[size++] = element;
037   }
038 
039   /**
040    * Liefert die Anzahl der Elemente in der Liste.
041    */
042   public int size()
043   {
044     return size;
045   }
046 
047   /**
048    * Liefert das Element an Position pos. Falls kein solches
049    * Element vorhanden ist, wird eine Exception ausgelöst.
050    */
051   public E elementAt(int pos)
052   {
053     if (pos >= size) {
054       throw new NoSuchElementException();
055     }
056     return (E)data[pos];
057   }
058 
059   /**
060    * Liefert einen Iterator zum Durchlaufen der Elemente.
061    */
062   public Iterator<E> iterator()
063   {
064     return new Iterator<E>()
065     {
066       int pos = 0;
067 
068       public boolean hasNext()
069       {
070         return pos < size;
071       }
072       public E next()
073       {
074         if (pos >= size) {
075           throw new NoSuchElementException();
076         }
077         return (E)data[pos++];
078       }
079       public void remove()
080       {
081         throw new UnsupportedOperationException();
082       }
083     };
084   }
085 
086   //------------------------------------------
087   public static void main(String[] args)
088   {
089     //Untypisierte Verwendung
090     MiniListe l1 = new MiniListe(10);
091     l1.addElement(3.14);
092     l1.addElement("world");
093     for (Object o : l1) {
094       System.out.println(o);
095     }
096     //Ganzzahlige Typisierung
097     System.out.println("---");
098     MiniListe<Integer> l2 = new MiniListe<Integer>(5);
099     l2.addElement(3);
100     l2.addElement(1);
101     l2.addElement(4);
102     for (Integer i : l2) {
103       System.out.println(i + 1000);
104     }
105     //Verwendung read-only
106     System.out.println("---");
107     MiniListe<? extends Number> l3 = l2;
108     for (Number i : l3) {
109       System.out.println(i.intValue() + 1000);
110     }
111   }
112 }
MiniListe.java
Listing 15.13: Eine eigene typisierte Listenklasse

Die Ausgabe des Programms ist:

3.14
world
---
1003
1001
1004
---
1003
1001
1004

Wir wollen uns einige interessante Implementierungsdetails ansehen:

Damit ist die komplette Schnittstelle der Klasse typsicher, und wir können sie wie in den vorigen Abschnitten beschrieben verwenden. Die main-Methode zeigt einige Anwendungen, die nach den bisherigen Ausführungen selbsterklärend sein sollten.

Zu beachten ist, dass in diesem Beispiel »nur« die öffentliche Schnittstelle der Klasse typsicher ist. Innerhalb der Klasse selbst ist es nach wie vor möglich, fehlerhaft typisierte Werte in das Datenarray einzufügen, denn als Object[] kann es beliebige Objekte aufnehmen. Wir könnten eine solche Schnittstelle sogar öffentlich machen:

public void addStringElement(String s)
{
  if (size >= data.length) {
    throw new ArrayIndexOutOfBoundsException();
  }
  data[size++] = s;
}

Dieser Code wäre vollkommen korrekt und würde vom Compiler nicht beanstandet werden. Ein Aufruf der Methode in einer MiniListe<Double> würde tatsächlich einen String einfügen, und beim Zugriff auf dieses Element würde es zu einer ClassCastException kommen. »Typsichere« Klassen sind also nur dann wirklich typsicher, wenn die Implementierung sicherstellt, dass keine typfremden Werte gespeichert werden.

 Warnung 

15.8.4 Typkompatibilität

Ober- und Unterklassen in generischen Typen

Beim Umgang mit typisierten Collections gibt es einige Besonderheiten, die zu einer Verkomplizierung des ursprünglichen Mechanismus geführt haben. Wir wollen zunächst eine einfache Methode betrachten, um uns noch einmal das Zusammenspiel zwischen Ober- und Unterklassen anzusehen (es wurde unter dem Stichwort »Polymorphismus« bereits in den Abschnitten Abschnitt 7.1.6 und Abschnitt 8.4 erläutert):

001 public static void doesWork1()
002 {
003   Double pi = new Double(3.14);
004   Number num = pi;
005   System.out.println(num.toString());
006   Double pi2 = (Double)num;
007 }
Listing 15.14: Funktionierendes Zusammenspiel von Ober- und Unterklassen

Zunächst wird eine Double-Variable pi angelegt und mit dem Fließkommawert 3.14 initialisiert. In der nächsten Zeile machen wir uns die Tatsache zunutze, dass Double eine Unterklasse von Number ist und weisen der Variablen der Oberklasse einen Wert der Unterklasse zu. Dies entspricht unserem bisherigen Verständnis von objektorientierter Programmierung, denn ein Double ist eine Number, hat alle Eigenschaften von Number (und ein paar mehr), und kann daher problemlos als Number verwendet werden. Die nächsten beiden Zeilen beweisen, dass diese Annahme korrekt ist (der Inhalt von num ist tatsächlich 3.14) und dass man die Number-Variable auch zurückkonvertieren kann - in Wirklichkeit zeigt sie ja auf ein Double.

Überträgt man das Beispiel auf typisierte Collections, lassen sich die dahinter stehenden Annahmen nicht ohne weiteres aufrecht erhalten. Ein Vector<Double> ist kein Subtyp eines Vector<Number>! Die folgenden Codezeilen sind illegal und werden vom Compiler abgewiesen:

Vector<Double> vd = new Vector<Double>();
Vector<Number> vn = vd;
 Warnung 

Wir wollen uns ansehen, warum das so ist. Wären sie nämlich erlaubt, könnten wir folgende Methode schreiben:

001 public static void doesntWork1()
002 {
003   Vector<Double> vd = new Vector<Double>();
004   Vector<Number> vn = vd;
005   vn.addElement(new Integer(7));
006   Double x = vd.elementAt(0);
007 }
Listing 15.15: Nicht funktionierendes Zusammenspiel von Ober- und Unterklassen

Das Programm erzeugt einen Vector<Double> vd und weist ihn einer Vector<Number>-Variable vn zu. Wäre diese eine Oberklasse von Vector<Double>, könnten wir natürlich auch Integer-Werte in den Vector einfügen wollen. Denn auch Integer ist eine Unterklasse von Number, und mit derselben Berechtigung würden wir annehmen, dass Vector<Number> Oberklasse von Vector<Integer> ist. Dann wäre aber nicht mehr sichergestellt, dass beim Zugriff auf vd nur noch Double-Elemente geliefert werden, denn über den Umweg vn haben wir ja auch ein Integer-Objekt eingefügt. Der Compiler könnte also nicht mehr garantieren, dass die vierte Zeile korrekt ausgeführt wird.

Daraus ist ein folgenschwerer Schluß zu ziehen: Ist U eine Unterklasse von O, so folgt daraus eben nicht, dass auch G<U> eine Unterklasse von G<O> ist. Das ist schwer zu verstehen, denn es widerspricht unseren bisherigen Erfahrungen im Umgang mit Ober- und Unterklassen. Das Problem dabei ist die Veränderlichkeit des Vectors, denn dadurch könnte man über den Alias-Zeiger vn Werte einzufügen, die nicht typkonform sind. Aus diesem Grunde würde der Compiler bereits die zweite Zeile der obigen Methode mit einem Typfehler ablehnen.

Der Wildcard ?

Diese neue Typinkompatibilität hat nun einige Konsequenzen, die sich vor allem dort bemerkbar machen, wo wir bisher intuitiv angenommen haben, dass Ober- und Unterklasse zuweisungskompatibel sind. Ein Beispiel ist etwa die Parametrisierung von Methoden, bei der man üblicherweise die formalen Parameter etwas allgemeiner fasst, um die Methode vielseitiger einsetzen zu können.

Betrachten wir eine Methode zur Ausgabe aller Elemente unserer Zahlenliste. Mit etwas Weitblick würde man sie so formulieren:

001 public static void printAllNumbers1(List<Number> numbers)
002 {
003   for (Number s : numbers) {
004     System.out.println(s);
005   }
006 }
Listing 15.16: Nicht funktionierende Ausgabe der Zahlenliste

Anstelle eines Vector<Double> verallgemeinern wir auf List<Number>. In der Annahme, dann nicht nur den Inhalt eines Vektors ausgeben zu können, sondern auch den einer ArrayList oder LinkedList (die beide ebenfalls vom Typ List sind), und zwar auch dann, wenn sie nicht Double-, sondern auch Integer- oder Long-Werte enthalten (die ebenfalls vom Typ Number sind). Diese Methode enthält nicht mehr Code als die speziellere Form, ist aber viel universeller einzusetzen. Ergo würde sich ein erfahrener Programmierer normalerweise dafür entscheiden, sie auf diese Weise zu parametrisieren.

Leider hat die Sache einen Haken. Zwar akzeptiert printAllNumbers1 beliebige Collections vom Typ List, aber eben nur, wenn sie Werte vom Typ Number enthalten. Solche mit Double-, Integer- oder Long-Werten lehnt sie - aus den oben beschriebenen Gründen - ab. Der praktische Nutzen dieser Methode ist damit nur mehr gering.

Um mehr Flexibilität zu gewinnen, wurde der Wildcard »?« als Typparameter eingeführt. Wird das Fragezeichen anstelle eines konkreten Elementtyps angegeben, bedeutet dies, das die Collection beliebige Werte enthalten kann:

001 public static void printAllNumbers2(List<?> numbers)
002 {
003   for (Object o: numbers) {
004     System.out.println(o);
005   }
006 }
Listing 15.17: Funktionierende Ausgabe der Zahlenliste

An diese Methode können nun Listen mit beliebig typisierten Elementen übergeben werden. Allerdings gibt es beim Lesen der Elemente keine Typsicherheit mehr. Am Kopf der for-Schleife kann man erkennen, dass der Compiler die Listenelemente nun - wie in früheren JDK-Versionen - lediglich als Werte des Typs Object ansieht. Spezielle Eigenschaften sind damit unsichtbar bzw. müssen mit Hilfe einer expliziten Typkonvertierung wieder sichtbar gemacht werden.

Gebundene Wildcards

Eine abgeschwächte Form des »?«-Wildcards sind die gebundenen Wildcards (»bounded wildcards«). Sie entstehen, wenn nach dem Fragezeichen das Schlüsselwort extends angegeben wird, gefolgt vom Namen des Elementtyps. Dadurch wird ausgedrückt, dass die Collection Elemente der angegebenen Klasse oder einer ihrer Unterklassen enthalten kann:

001 public static void printAllNumbers3(List<? extends Number> numbers)
002 {
003   for (Number s : numbers) {
004     System.out.println(s.doubleValue());
005   }
006 }
Listing 15.18: Verbesserte funktionierende Ausgabe der Zahlenliste

Gebundene Wildcards realisieren am ehesten die bisher bekannten Regeln von Typkonformität bei Ober- und Unterklassen von Elementen. ? extends O bedeutet, dass die Collection Elemente des Typs O oder einer (auch über mehrere Stufen) daraus abgeleiteten Unterklasse enthalten kann. An einen formalen Parameter vom Typ List<? extends Number> können also aktuelle Parameter des Typs Vector<Double>, ArrayList<Integer> usw. übergeben werden.

Zu beachten ist allerdings, dass die Wildcards nur das Lesen der Elemente flexibilisieren. Das Einfügen neuer Elemente ist dagegen nicht mehr erlaubt und wird vom Compiler unterbunden. Genauer gesagt, verboten ist der Aufruf von Methoden, die mindestens einen generisch typisierten Parameter haben. Die Gründe entsprechen den oben erläuterten. Wäre es nämlich zulässig, in eine List<? extends Number> einen Double einzufügen, so würde ein Problem entstehen, wenn es sich tatsächlich beispielsweise um einen Vector<Integer> handeln würde, denn dieser darf ja kein Element des Typs Double aufnehmen. Daher sorgt der Compiler dafür, dass bei der Verwendung von Wildcards und gebundenen Wildcards die Collection nur noch zum Lesen der Elemente verwendet werden darf. Versuche, neue Elemente einzufügen, werden mit einem Compilerfehler quittiert.

 Hinweis 

15.8.5 Sonstiges

Die Implementierung von generischen Collections ist in Java im Prinzip Sache des Compilers, die virtuelle Maschine merkt davon nichts. Der Compiler interpretiert den Quelltext, prüft die Typ-Parameter und erzeugt Bytecode mit den erforderlichen Typ-Konvertierungen, Warnungen und Fehlermeldungen. Das Laufzeitsystem, die virtuelle Maschine, arbeitet dabei im Grunde wie in früheren JDK-Versionen. Dabei bleibt insbesondere die Integrität der VM stets erhalten und Typfehler führen zu kontrollierten (ClassCast-) Exceptions, wie in früheren JDK-Versionen. Trotz allen Aufwands lassen sich nämlich Fälle konstruieren, bei denen fehlerhaft typisierte Werte in eine generische Collection eingefügt werden. Wir werden dazu im nächsten Abschnitt ein einfaches Beispiel sehen.

Anders als Templates in C++ erzeugen generische Java-Klassen keinen zusätzlichen Programmcode. Alle Instanzen einer generischen Klasse verwenden denselben Bytecode, getClass liefert ein und dasselbe Klassenobjekt und die statischen Variablen werden gemeinsam verwendet. Es ist nicht erlaubt, in statischen Initialisierern oder statischen Methoden auf den Typparameter einer Klasse zuzugreifen, und die Anwendung des instanceof-Operators auf eine typisierte Klasse ist illegal.

Es ist zulässig, generische Collections und herkömmliche Collections gemeinsam zu verwenden. Erwartet beispielsweise ein Methodenparameter eine typisierte Collection, so kann auch eine untypisierte Collection übergeben werden, und umgekehrt. In diesem Fall kann der Compiler die Typsicherheit des Programmcodes allerdings nicht mehr sicherstellen und generiert vorsichtshalber eine »unchecked warning« :

Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Durch Rekompilieren mit -Xlint:unchecked werden Detailinformationen ausgegeben. Die Entwickler des JDK gehen davon aus, dass in Zukunft nur noch typisierte Collections verwendet werden, und diese Warnungen nach einer gewissen Übergangszeit der Vergangenheit angehören werden.

Neben den hier beschriebenen Eigenschaften gibt es noch eine ganze Reihe weiterer Aspekte von generischen Klassen, auf die wir hier nicht näher eingehen wollen. Sie werden meist gebraucht, um spezielle Sonderfälle bei der Entwicklung von Collection-Klassen zu realisieren; für »Otto Normalprogrammierer« sind die meisten von ihnen weniger relevant. Die nachfolgende Aufzählung listet einige von ihnen auf, weitere Informationen können der Sprachspezifikation bzw. der Dokumentation der J2SE 5.0 oder 6.0 entnommen werden:


 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 5. Auflage, Addison Wesley, Version 5.0.2
 <<    <     >    >>   API  © 1998, 2007 Guido Krüger & Thomas Stark, http://www.javabuch.de