17 Java – Generics
Was sind Generics?
Generics wurden in Java 5 hinzugefügt, um Klassen, Interfaces und Methoden zu programmieren, die den Umgang mit verschiedenen Datentypen, mittels Typ-Parametern, auf einer höheren generischen Abstraktionsebene ermöglichen.
Sie sorgen für Typsicherheit, da die Typüberprüfung bereits zur Kompilierungszeit erfolgt. Dadurch wird das Risiko von Laufzeitfehlern reduziert. Auch die Notwendigkeit von Type Castings entfällt, da der Typ einer Klasse oder Methode klar definiert ist.
Generische Methoden
Nehmen wir an, wir haben verschiedene Arrays mit unterschiedlichen Typen und wir wollen alle Elemente jedes Arrays einfach nur auf der Konsole ausgeben. Dann würden wir normalerweise Folgendes schreiben.
Da beide Arrays unterschiedliche Typen verwalten, müssen wir auch zwei verschiedene Methoden implementieren, die mit den entsprechenden Typen umgehen können.
Bei genauerer Betrachtung könnte jedoch auffallen, dass wir in beiden printArray()
-Methoden genau dieselbe Logik implementiert haben. Wir haben also redundanten Code. Generics lösen dieses Problem.
Wir entfernen eine der printArray()
- Methoden und deklarieren die verbleibende Methode als generische Methode mit Hilfe des Diamond-Operators <>
, indem wir den generischen Typ T
vor den Rückgabetyp setzen. Der Parameter wird entsprechend zum Typparameter T[]
angepasst. Damit kann die Methode nun Arrays beliebiger Typen entgegennehmen. Die Logik der Methode bleibt unverändert und wir sparen uns doppelten Code.
Ein Punkt ist jedoch noch zu beachten. Bei der Verwendung der generischen Methode muss das int
-Array durch den entsprechenden Wrapper-Typ Integer
ersetzt werden. Das liegt daran, da Generics nur mit Referenztypen arbeiten können, nicht mit primitiven Typen wie int
.
Auch die Angabe von mehreren Typparametern durch Kommas getrennt ist möglich.
Beschränkte Typparameter
Es kann vorkommen, dass ihr die Arten von Typen einschränken möchtet, die an einen Typparameter übergeben werden sollen. Wenn beispielsweise eine generische Methode nur mit Zahlen arbeitet, soll diese möglicherweise auch nur Instanzen der Klasse Number
oder deren Unterklassen akzeptieren. Dafür sind Bounded Type Parameters (beschränkte Typparameter) gedacht.
Da wir in diesem Fall festlegen, dass T
nur Instanzen der Klasse Number
annehmen darf, können wir innerhalb der Methode auch auf die entsprechenden doubleValue()
-Methoden zugreifen. Diese sind in der Number
-Klasse deklariert und werden in den Unterklassen, also den entsprechenden Wrapper-Klassen für numerische Werte, überschrieben.
Weiterführende Frage
Jetzt könnte man sich fragen, warum es in der Math
- Klasse für jede der numerischen primitiven Typen eine eigene Methode max()
gibt. Unsere obige max()
-Methode bietet doch eine viel kürzere Lösung.
Das stimmt. Dennoch gibt es gleich zwei Gründe, weshalb die spezialisierten max()
-Methoden der Math
-Klasse weiterhin relevant sind.
Zum einen existiert Java schon recht lange und die ursprünglichen Versionen von Java besaßen noch keine Generics. Da Java stets auf Rückwärtskompatibilität bedacht war, wurden mit der Einführung von Generics in Java 5 die APIs so angepasst, dass sie die neuen Sprachfunktionen unterstützen, ohne bestehenden Code zu beeinträchtigen.
Diemax()
- Methoden für primitive Datentypen, so wie viele weitere Methoden, sind also Bestandteil des ursprünglichen Designs. Sie wurden beibehalten, um sicherzustellen, dass ältere Programme auch in neueren Laufzeitumgebungen korrekt funktionieren.
Java 5 ist mittlerweile veraltet und man könnte annehmen, dass alle Software-Projekte auf modernere Java-Versionen migriert sind, nicht wahr? Well ...

Das Updaten von Code-Basen auf neuere Versionen ist tatsächlich ein allgemeines Problem und ja, viele Entwicklerteams sitzen noch auf älteren Java-Versionen fest. Aber diese Problematik würde hier den Rahmen sprengen. Allerdings führt es uns direkt zum zweiten Grund, wenn die vorherige Problematik wegfallen würde.
In unserer generischen Implementierung der
max()
- Methode werden die Werte als Referenztypen behandelt, also als Objekte. Konkret handelt es sich umT extends Number
, also Instanzen der KlasseNumber
. Um die Werte zu vergleichen, muss für jedes Objekt die MethodedoubleValue()
aufgerufen werden.
Und das wiederum führt zu einem Overhead, da Objekte zusätzlich Ressourcen benötigen. Im Gegensatz dazu arbeiten die spezialisiertenmax()
-Methoden derMath
-Klasse direkt mit primitiven Datentypen wieint
oderdouble
. Dies erfordert weder Autoboxing noch Unboxing, sodass die Werte ohne Umweg direkt verglichen werden können. Das spart Zeit und Ressourcen.
Multiple Typbeschränkung
Generics lassen sich nicht nur durch einen einzigen Typ beschränken. Wenn ein Typparameter bestimmte Eigenschaften oder Objektmethoden mehrerer Klassen oder Interfaces erfüllen soll, ist es besonders nützlich, mehrere Typbeschränkungen zu verwenden.
Dabei gilt: Ein Typparameter kann eine Klasse und beliebig viele Interfaces als Begrenzung haben. Dabei muss jedoch die Reihenfolge eingehalten werden, sollte die Begrenzung sowohl von einer Klasse, als auch von mehreren Interfaces abhängen → zuerst die Angabe der Klasse, dann die Angabe des oder der Interfaces.
Die Methode train()
stell hier sicher, dass das übergebene Objekt
vom Typ
Animal
ist,das Interface
Flyable
implementiert unddas Interface
Swimmable
implementiert.
Generische Klassen
Es sind jedoch nicht nur generische Methoden möglich, sondern auch generische Klassen und Interfaces. Eine generische Klasse sieht genauso aus, wie eine normale Klasse. Allerdings befindet sich hinter dem Klassennamen noch ein Typparameterabschnitt. Wie bei generischen Methoden kann der Typparameterabschnitt einer generischen Klasse einen oder mehrere durch Kommas getrennte Typparameter enthalten.
Solche Klassen werden als parametrisierte Klassen oder parametrisierte Typen bezeichnet, da sie einen oder mehrere Parameter akzeptieren. Auch hier können die Klassen mit beschränkten Typparametern versehen werden. Dazu ein sehr einfaches Beispiel aus einem kleinen Projekt von mir.
Die parametrisierte Klasse Focus<T extends Entity>
nutze ich im Rahmen eines Kamerasystems innerhalb eines Spiels. Dadurch kann ich einer Klasse Camera
mitteilen, auf welchem Entity
sie den Fokus behalten soll. Standardmäßig liegt dort der Fokus auf der Player
-Klasse, welche wiederum von Entity
erbt, sodass die Kamera dem Spieler folgt.
Weiterführende Frage
Einige Fortgeschrittenere unter euch könnten sich jetzt vielleicht fragen, warum ich nicht einfach die Klasse Optional<T>
verwende. Diese bringt tatsächlich genau die Funktionalität mit, die ich bräuchte.
Wieso ich für diesen Zweck eine eigene Klasse geschrieben habe, hat mehrere Gründe.
Einer davon ist die Tatsache, dass
Optional<T>
primär als Rückgabetyp bei Methoden gedacht ist, um die Absenz eines fehlenden Wertes auszudrücken.Focus<T extends Entity>
verwende ich jedoch als Objektvariablen bzw. Attribut innerhalb der KlasseCamera
.
Um darauf hinzuweisen, dassOptional<T>
nicht für die Modellierung von Objektzuständen gedacht ist, gibt IntelliJ tatsächlich eine Warnung aus, sollte die Klasse entgegen der Konvention als Attribut verwendet werden.Außerdem wollte ich den Fokus der Kamera nur für Objekte der Klasse
Entity
und deren Unterklasse zulassen.Optional<T>
lässt hingegen jede Art von Typ zu, da diese keinen beschränkten Typparameter besitzt.Schlussendlich lässt sich aus meiner Klasse noch ableiten, dass ich einen "Freien Modus" implementieren wollte. Es sollte die Möglichkeit geben die Kamera flexibel zu steuern, ohne an eine bestimmte Entität gebunden zu sein.
Wildcards
Es kann vorkommen, dass wir an manchen Stellen im Code mit Objekten arbeiten, dessen Typ wir jedoch nicht genau kennen. Zu diesem Zweck wurden Wildcards implementiert, die eine flexiblere Architektur ermöglichen. Eine Wildcard wird mittels Fragezeichen ?
dargestellt und steht für einen unbekannten generischen Typ. Sie erlauben es, generische Typen flexibler zu handhaben, indem sie eine gewisse Variabilität bei Typangaben zulassen, ohne dass der genaue Typ festgelegt werden muss.
Wildcards lassen sich dabei in drei Arten unterteilen.
Unbounded Wildcards
Unbounded Wildcards ?
stehen für einen beliebigen Typ, ohne Einschränkung. Wenn der genaue Typ nicht wichtig ist, sondern es sich nur um irgendeinen generischen Typ handeln muss, dann wird diese Art verwendet.
Ein Beispiel wäre eine Methode, welche eine Liste von beliebigen Objekten akzeptiert, ohne auf deren Typ zuzugreifen.
Die Methode printList()
akzeptiert eine Liste beliebigen Typs, ohne die Elemente der Liste zu verändern oder spezifisch zu behandeln.
Upper Bound Wildcards
Upper Bound Wildcards werden mit ? extends T
definiert, wobei T
die Obergrenze repräsentiert. Das bedeutet, dass die Wildcard einen Typ repräsentiert, welcher T
selbst oder eine Unterklasse von T
darstellt. Schauen wir uns dazu das vorherige Beispiel an und modifizieren es etwas.
In diesem Beispiel kann die Methode Listen vom Typ Number
oder beliebigen Unterklassen (z. B. Integer
, Byte
, Float
etc.) entgegennehmen. Das macht dann Sinn, wenn Elemente nur gelesen, aber nicht sicher hinzugefügt werden können.
Lower Bound Wildcards
Lower Bound Wildcards werden mit ? super T
definiert und legen für den Typ eine Untergrenze fest. Die Wildcard repräsentiert also einen Typ, der T
selbst oder eine Oberklasse von T
darstellt. Sollen Elemente einer Datenstruktur hinzugefügt werden, ist es sinnvoll, diese Art zu verwenden, da sicher ist, dass die Struktur Objekte vom Typ T
oder einer Oberklasse akzeptiert.
Type Erasure – Typlöschung
Type Erasure beschreibt den Prozess, bei dem der Compiler Typinformationen generischer Klassen und Methoden zur Laufzeit entfernt. Nach der Überprüfung der Typen und ihrer Verwendung ersetzt der Compiler alle Typparameter durch deren obere Typschranken sowie durch entsprechende explizite Cast-Operationen im Bytecode
Die Klasse Optional<T>
Weiter oben im Abschnitt "Generische Klassen: Weiterführende Frage" bin ich bereits ein wenig auf die Klasse Optional<T>
eingegangen.
Die Klasse Optional<T>
wurde in Java 8 eingeführt und ist als moderne Alternative für die Methodenrückgabe anstelle von null
vorgesehen.