Generics in Java 1.5

 


... [ Seminar BS, WWW und Java ] ... [ Thema Generics in Java ] ... [ Detaillierte Betrachtung ] ...

Erste Beispiele


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:
1: int sum(List<Integer> intList) {
2: int result = 0;
3: for(int i=0;i<intList.size();i++)
4: result += intList.get(i).intValue();
5: return result;
6: }
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:
1: List<Integer> myIntList = new Vector<Integer>();
2: myIntList.add(new Integer(1234));
3: myIntList.add(new Integer(5678));
4: myIntList.add(new Integer(4711));
5:
6: System.out.println("Summe: " + sum(myIntList));
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:
1: public interface List<E> extends Collection<E> {
2: // ...
3: boolean add(E o);
4:
5: // ...
6: E get(int index);
7:
8: // ...
9: }
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:
1: public class LinkedList<E> {
2:
3: private E value;
4: private LinkedList<E> next;
5:
6: public LinkedList() {
7: }
8:
9: public boolean isEmpty() {
10: return value == null;
11: }
12:
13: public void add(E value) {
14: // letztes Element?
15: if(isEmpty()) {
16: this.value = value;
17: // neues, leeres Element anhängen
18: next = new LinkedList<E>();
19: }
20: else
21: next.add(value);
22: }
23:
24: public E get(int index) {
25: if(index < 0 || isEmpty())
26: throw new IndexOutOfBoundsException();
27:
28: if(index == 0)
29: return value;
30:
31: return next.get(index - 1);
32: }
33:
34: public int size() {
35: if(isEmpty())
36: return 0;
37:
38: return 1 + next.size();
39: }
40: }
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:
1: double sum(List<Number> numList) {
2: double result = 0;
3: for(int i=0;i<numList.size();i++)
4: result += numList.get(i).doubleValue();
5: return result;
6: }
Ein korrekter Aufruf könnte z. B. so aussehen:
1: List<Number> myNumList = new Vector<Number>();
2: myNumList.add(new Integer(4711));
3: myNumList.add(new Float(4.2f));
4: myNumList.add(new Long(Long.MAX_VALUE));
5:
6: System.out.println("Summe: " + sum(myNumList));
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:
1: List<Integer> myIntList = new Vector<Integer>();
2: myIntList.add(new Integer(1234));
3: myIntList.add(new Integer(5678));
4: myIntList.add(new Integer(4711));
5:
6: System.out.println("Summe: " + sum(myIntList)); // Error: sum(List<Number>) cannot be applied to List<Integer>
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.

... [ Seminar BS, WWW und Java ] ... [ Thema Generics in Java ] ... [ Erste Beispiele ] ... [ Detaillierte Betrachtung ] ...

Valid XHTML 1.0 Strict