Summation mit Generics
Zurück zum Beispiel aus dem letzten Kapitel: Eine Funktion, die eine Liste von Integer-Objekten annimmt und die Summe dieser Zahlen zurückgibt. Unter Verwendung von Generics würde diese Funktion nun wie folgt aussehen:
In Zeile 1 wird für den Parameter der Funktion festgelegt, dass es ein Objekt vom Typ
List
ist (diese Bedingung erfüllen z. B. die konkreten Klassen
Vector
und
LinkedList
aus dem Paket
java.util
) und mit dem Typen
Integer
parametrisiert ist.
Unter dieser Bedingung liefert die Methode
get()
in Zeile 4 offensichtlich ein
Integer
-Objekt, da direkt und ohne Downcast hierauf die Methode
intValue
angewendet werden kann.
Die Vorteile sind leicht ersichtlich: Der Code wurde gegenüber dem letzten Beispiel aus dem letzten Kapitel stark reduziert, und dennoch ist die Typsicherheit sichergestellt (nun sogar bereits zur Compilezeit).
Ein Beispielaufruf dieser Funktion:
In Zeile 1 wird eine Variable vom Typ
List<Integer>
definiert und mit der Referenz auf ein neues Objekt vom Typ
Vector<Integer>
initialisiert. Man sieht hier also bereits die Syntax, wie bei der Objektkonstruktion der Typparameter übergeben wird. Die Klammern für den Konstruktoraufruf (inklusive eventueller Parameter) stehen nun ganz am Ende; der Typparameter
Integer
steht zwischen dem eigentlichen Klassennamen und den Konstruktorklammern.
Über das Objekt, das über die Variable
myIntList
referenziert wird, ist von diesem Punkt an nun also bekannt, dass es die Methoden des Interface
List
implementiert und mit dem Typen
Integer
parametrisiert ist. Schauen wir uns nun einmal das Interface
List
in Auszügen an, um nachzuvollziehen, wie sich diese Parametrisierung auswirkt:
In Zeile 1 wird festgelegt, dass das Interface über einen Typparameter, hier E, verfügt und vom Interface
Collection
erbt. Da dieses Interface auch einen Typparameter hat, wird der Typparameter, mit dem
List
parametrisiert wird, direkt an
Collection
weitergereicht, indem er hier wiederholt wird.
Das erstmalige Vorkommen von E ist also die Definition, die Namensfestlegung. Ab hier ist E ein Typparameter der gesamten Klasse bzw. hier des gesamten Interface und kann innerhalb des Interface überall wie ein normaler Typ verwendet werden (mit Ausnahmen; dazu später mehr). Die erste Verwendung ist bereits das Weiterreichen an Collection. Ein konkretes
List<Integer>
erbt also nicht von irgendeiner Collection, sondern von
Collection<Integer>
. Allgemein kann man sich die Parametrisierung mit einem konrekten Typ also vorstellen wie ein "Suchen und Ersetzen" der Vorkommen von E durch
Integer
.
In Zeile 3 wird E als Typangabe für den Parameter der
add()
-Methode verwendet. In obigem Beispiel ist daher klar, dass die
add()/
-Methode nur Integer-Objekte akzeptiert.
In Zeile 6 bezeichnet E den Rückgabewert der Methode
get()
. Daher ist im obigen Beispiel bekannt, dass diese Methode immer ein Integer-Objekt liefert, weswegen auf den Rückgabewert auch sofort die Methode
intValue()
angewendet werden kann.
Zur Namenskonvention: Es wird vorgeschlagen, für Typparameter immer ein- und großbuchstabige Bezeichner zu verwenden. Containerklassen und -schnittstellen verwenden meist ein E für "Element", da der Typparameter den Typ der enthaltenen Elemente bezeichnet. Andere parametrisierte Klassen verwenden meist ein T für "Typ". Das Interface
Map
verwendet aber noch andere Bezeichner: Da es mit zwei Typparametern parametrisiert ist, werden diese K und V (für Key und Value) genannt.
Eine erste generische Klasse
Mit dem bisher gezeigten Elementen von Generics ist es nun möglich, eine erste eigene generische Klasse zu schreiben. Dies soll eine verkettete Liste sein, die ebenfalls einen Typparameter annehmen soll, mit dem der Typ der Werte in der Liste angegeben können werden soll. Damit es nicht zu kompliziert wird, sollen in diese Liste nur Elemente eingefügt und ausgelesen werden können. Die Klasse für diese Liste könnte wie folgt aussehen:
In diesem Beispiel werden die vielen Einsatzmöglichkeiten des Typparameters innerhalb der Klasse deutlich. In Zeile 3 wird eine private Objektvariable vom Typ des Typparameters definiert. In dieser Variable wird zur Laufzeit der Wert an der aktuellen Stelle der verketteten Liste gespeichert.
In Zeile 4 wird ebenfalls eine private Objektvariable definiert. Diese ist eine Referenz auf die nachfolgende verkettete Liste. Diese wird hier ebenfalls so definiert, dass sie mit dem gleichen Typparameter wie die aktuelle Instanz der verketteten Liste parametrisiert sein soll.
In Zeile 13 wird der Typparameter, wie bereits aus dem Interface
List
bekannt, genutzt, um den Typen des Parameters der
add()
-Funktion zu spezifizieren. In Zeile 16 dann wird der privaten Objektvariable
value
der Wert des Parameters übergeben, was typsicher ist, da ja der Parameter vom gleichen Typ wie die Objektvariable ist.
In Zeile 18 ist zu sehen, wie der Typparameter auch dazu genutzt werden kann, weitere Objekte zu parametrisieren. Hier wird eine neue Instanz der verketteten Liste erzeugt und der Typparameter genutzt, das neue Objekt zu parametrisieren.
Schließlich wird in Zeile 24 der Typparameter als Angabe des Rückgabewertes der
get()
-Methode genutzt, wie es ebenfalls schon aus dem Interface
List
bekannt ist.
Probleme
Denkt man zurück an die Bibliotheksfunktion, die die Elemente einer Liste summieren sollte, könnte man auf die Idee kommen, diese Funktion so zu gestalten, dass beliebige Zahlenobjekte, also nicht nur
Integer
, sondern auch
Long
,
Float
und ähnliche Objekte summiert werden können. Alle diese Klassen haben als abstrakte Basisklasse
Number
. Diese definiert bereits die Methoden
intValue()
,
doubleValue()
usw., so dass man von Objekten, die zuweisungskompatibel zu
Number
sind, weiß, dass sie diese Methoden implementieren.
Nimmt man weiterhin
double
als primitiven Typ, der alle anderen Wertebereiche umfaßt, an, könnte man die Funktion nun wie folgt gestalten:
Ein korrekter Aufruf könnte z. B. so aussehen:
Es wird hier also eine Liste erzeugt, die Objekte aufnehmen kann, die zuweisungskompatibel zu
Number
sind. Da
Number
selbst eine abstrakte Klasse ist, sind dies die Unterklassen
Integer
,
Long
,
Double
usw.
Man könnte nun auf die Idee kommen, diese Methode auch wie folgt aufzurufen:
Man erhält jedoch die in Zeile 6 erwähnte Compilermeldung, dass die Liste von
Integer
-Objekten nicht zuweisungskompatibel zu einer Liste von
Number
-Objekten sei. Dies mag auf den ersten Blick überraschen. Warum dies so ist, wird im nächsten Kapitel untersucht.