Generische Klassen und Interfaces
Mittels Klassen und Interfaces können wir eigene Datentypen definieren.
Dies gibt uns die Möglichkeit, Konzepte, die wir in unseren Programmen umsetzen wollen, präzise zu modellieren.
Häufig tritt bei der Definition von Konzepten das Problem auf, dass wir dasselbe Konzept in zwei ähnlichen Ausprägungen brauchen.
Dabei unterscheiden diese sich nur durch einen anderen Typ.
Ein typisches Beispiel bildet der Datentyp List
, dessen Operationen wir meist in einem Interface definieren:
interface List {
// adds an Element to the list
void add(String element);
// returns the element at the given position
String get(int index);
// ...
}
Wie Sie sehen erzwingt diese Definition, dass die Elemente, die von der Liste verwaltet werden, vom Typ String
sind.
Nützlich sind aber nicht nur Listen von Strings.
Listen sollten auch andere Typen unterstützen wie zum Beispiel Zahlen, Personen, oder Telefonnummern.
Mit unserem bisherigen Wissen könnten wir dieses Problem auf zwei Arten lösen:
- Wir schreiben für jeden Datentyp eine eigene Klasse, oder ein eigenes Interface.
- Wir definieren die Elemente als Typ
Object
oder eines anderen gemeinsamen Supertyps der Elemente.
Beide Optionen sind unbefriedigend. In der ersten Option kopieren wir viel Code. Die Programme werden unübersichtlich und schwierig zu verändern. Jede Änderung an einer Listenfunktionalität muss an vielen Stellen konsistent nachgezogen werden. Bei der zweiten Option verlieren wir die Präzision. Statt dass wir Ausdrücken können, dass wir eine Liste von Strings verwalten, oder eine Liste von Personen, drückt unser Code nur noch aus, das eine Liste von Objects verwaltet wird. Der Compiler ist nicht mehr in der Lage zu überprüfen, dass in der Liste wirklich Objekte vom richtigen Typ sind.
Definition generischer Typen mittels Generics
Die meisten modernen Programmiersprachen bieten für dieses Problem die Lösung von parametrischen Typen an. In Java ist das Konstrukt unter dem Namen Generics bekannt.
Die Idee hinter parametrischen Typen ist einfach: Wenn wir in obigem Beispiel die Liste definieren, führen wir einen Parameter ein, der für den Typ der Elemente steht. Dieser Parameter wird Typparameter genannt. Wenn wir dann eine konkrete Liste verwenden wollen, müssen wir für diesen Parameter einen konkreten Wert angeben.
In unserem Listenbeispiel könnte das wie folgt aussehen:
interface List<E> {
// adds an Element to the list
void add(E element);
// returns the element at the given position
E get(int index);
}
Beachten Sie, dass wir in der Klassendefinition mit der Syntax <E>
einen Typparameter mit dem Namen E
eingeführt haben.
Der Typ E
steht nun für einen fixen, aber zur Definitionszeit noch unbekannten Typ.
Er nimmt den Platz ein, an dem vorher ein konkreter Typ wie String
oder Integer
gestanden hat.
Arbeiten mit generischen Typen
Ein generischer Typ ist nicht vollständig. Er hat einen, oder mehrere, freie Typparameter. Deshalb müssen wir bei der Nutzung dieses Typs einen konkreten Typ für den Typparameter angeben.
Um eine Variable vom Typ List zu definieren, würden wir folgendes Schreiben:
// Deklariert Variable vom Type List<String>
List<String> stringList;
Sie sehen, wir schreiben bei der Nutzung der Klasse einfach zwischen <>
den entsprechenden, konkreten Typ.
Wir gehen genau so vor, wenn wir einen generischen Typ als Rückgabewerte oder Parameter einer Methoden angeben wollen:
List<Double> reverse(List<Double> l) {
// Implementation
}
Mit genau demselben Prinzip können wir auch für generische Interfaces den konkreten Typ bei der Implementation angeben:
// Definition einer konkreten LinkedList Klasse die das Interface definiert
class LinkedListOfDoubles implements List<Double> {
// Implementation
}
Hier haben wir den Typ explizit auf Double
festgelegt.
Wir könnten natürlich auch die implementierende Klasse LinkedList
selbst wieder parametrisch machen:
class LinkedList<E> implements List<E> {
// Implementation
}
In diesem Beispiel entspricht E
wieder einem freien Typparameter.
Zum Schluss zeigen wir, wie wir Werte von diesem Typ erzeugen können:
// Deklariert Variable vom Typ List<Integer> und initialisiert diese mit einer leeren verketteten Liste
List<Integer> intList = new LinkedList<Integer>();
Generics in der Java Klassenbibliothek
Wenn wir uns in der API-Dokumentation die Dokumentation des Interface List und der LinkedList anschauen, dann sehen wir, dass diese so wie oben besprochen, generisch definiert sind.
Generics, primitive Typen und autoboxing
Wir erinnern uns, dass Java zwischen primitiven Typen und Referenztypen unterscheidet. Die primitiven Typen verhalten sich in vielen Fällen etwas anders. Dies ist leider auch bei Generics der Fall.
Als Typparameter für einen parametrischen Datentyp dürfen nur Referenztypen eingesetzt werden.
Wir können also einen Typ List<Integer>
nutzen, nicht aber den Typ List<int>
.
Das klingt auf den ersten Blick wie eine grosse Einschränkung.
Wir können somit als Elemente nicht Werte vom Typ int
, sondern nur Integer
verwenden.
Dies ist aber, zum Glück, nicht so schlimm.
Primitive Typen werden von Java automatisch in den entsprechenden Referenztyp umgewandelt.
Dieses Verhalten nennt sich autoboxing (der primitive Wert wird automatisch in die Objektbox gesteckt).
Deshalb funktioniert folgender Code:
List<Integer> list = new LinkedList<Integer>();
int e = 5;
list.add(e); // e wird automatisch in einen Integer umgewandelt.
Experimente
Vertiefen Sie das Gelernte, indem Sie direkt damit experimentieren:
Mehrere Typparameter
Bisher haben wir uns nur Beispiele angeschaut, bei denen wir einen Typparameter haben.
Ein Datentyp kann aber verschiedene Typparamter definieren.
Wie wir dies mit Parameter von Funktionen gemacht haben, trennen wir dafür die Parameter einfach durch ein Komma.
Als Beispiel schauen wir uns eine Klasse Tuple
an, welche zwei Werte beliebigen Typs speichert:
class Tuple<T, S> {
T first;
S second;
Tuple(T first, S second) {
this.first = first;
this.second = second;
}
}
Die Nutzung dieser Klasse bringt keinerlei Überraschungen:
Tuple<String, Integer> stringIntegerTuple = new Tuple<String, Integer>("a string", 5);
Experimentieren Sie auch mit dieser Klasse.
Haben Sie Fragen oder Bemerkungen? Schreiben Sie diese doch ins Forum.