06.03 Wildcards und Bounded Type Parameters
Im letzten Kapitel haben Sie etwas über Generics gelernt und dabei die Klasse GenerischeKlasse<Generic>
geschrieben. Rufen Sie sich diese Klasse nochmal ins Gedächtnis – Sie werden sie jetzt abermals benötigen. Denn Wildcards und Bounded Type Parameters (welche ebenfalls mit Java 1.5 eingeführt wurden) erweitern die Funktionalität von Generics.
Wildcards
Angenommen Sie wollen unsere generische Klasse in einer Methode einer anderen Klasse manipulieren, dann muss diese Methode ja auch die generische Klasse mit jedem generischen Typ annehmen. Dies funktioniert so aber nicht. Testen Sie hierzu folgenden Code:
public class WildcardTest { public static void main(String[] args) { GenerischeKlasse<String> stringGeneric = new GenerischeKlasse<String>(); GenerischeKlasse<Integer> integerGeneric = new GenerischeKlasse<Integer>(); manipulateGenClass(stringGeneric); manipulateGenClass(integerGeneric); } private static void manipulateGenClass(GenerischeKlasse<String> str) { // ... } }
Selbstverständlich lässt sich diese Klasse nicht kompilieren, da die Methode manipulateGenClass
eine GenerischeKlasse
mit einem String
als generischen Typen erwartet, wir aber versuchen zusätzlich eine GenerischeKlasse
mit dem generischen Typ Integer
durch diese Methode manipulieren zu lassen. Nur wie bringen wir diese Methode nun sauber dazu, dass sie alle Objekte unserer Klasse GenerischeKlasse
akzeptiert? Ein weiterer Versuch wäre es unsere Methode so umzuschreiben:
private static void manipulateGenClass(GenerischeKlasse<Object> obj) { // ... }
Schließlich sind Strings
und Integer
auch Objects
. Dies funktioniert zwar mit Arrays, nicht aber mit generischen Klassen, da Arrays während der Laufzeit auf den richtigen Typ geprüft werden können, dies bei Generics aber nur durch den Compiler übernommen und nicht zur Laufzeit getestet werden kann (siehe im letzten Kapitel unter dem Punkt Type Erasure). An dieser Stelle kommt der große Auftritt von Wildcards! Anstelle eines konkreten Typs kann in die eckigen Klammern ein Fragezeichen gesetzt werden. Dadurch wird jeder beliebige generische Typ akzeptiert:
private static void manipulateGenClass(GenerischeKlasse<?> gen) { // ... }
Bitte beachten Sie, dass auch folgendes kompilierbar, wenn auch nur bedingt sinnvoll, ist:
GenerischeKlasse<?> stringGeneric = new GenerischeKlasse<String>(); GenerischeKlasse<?> integerGeneric = new GenerischeKlasse<Integer>();
Wohingegen der Compiler bei diesem Code eine Fehlermeldung beim Kompilieren ausgeben würde:
GenerischeKlasse<?> stringGeneric = new GenerischeKlasse<?>(); GenerischeKlasse<?> integerGeneric = new GenerischeKlasse<?>();
Diesen Typen nennt man dann unbeschränkter Wildcard Typ. Wie Sie sich evtl. gerade gedacht haben, besteht in Java auch die Möglichkeit Wildcards zu beschränken. Dies kann auf zwei verschiedene Arten geschehen:
- Beschränkung auf einen Typ und dessen Kindtypen (nach oben beschränkter Wildcard Typ)
- Beschränkung auf einen Typ und dessen Supertypen (nach unten beschränkter Wildcard Typ)
Sehen wir uns zuerst den nach oben beschränkten Wildcard Typ an, welchen Sie vermutlich auch am Häufigsten einsetzen werden. Dieser wird immer dann verwendet, wenn nur generische Typen eines bestimmten Typs und dessen Untertypen zulässig sind, bzw. Typen, die das angegebene Interface implementieren. Dabei wird das Fragezeichen um ein extends Typ
ergänzt. Würden wir unsere Methode nun so umschreiben,
private static void manipulateGenClass(GenerischeKlasse<? extends CharSequence> cs) { // ... }
könnten wir der Methode ohne Probleme unsere Klasse mit dem String
als generischen Typen übergeben, da String
das Interface CharSequence
implementiert. Integer
implementiert hingegen dieses Interface nicht, weshalb unsere Klasse mit diesem generischen Typen der Methode auch nicht übergeben werden kann.
Um Ihr Wissen über Wildcards zu vervollständigen, betrachten wir noch den nach unten beschränkten Wildcard Typen. Diesen werden Sie in der Praxis wohl kaum verwenden, da sein Einsatz nur bei den wenigsten Gegebenheiten sinnvoll ist – nämlich immer dann, wenn nur generische Typen eines bestimmten Typs und dessen Supertypen verwendet werden dürfen. Ersetzen Sie das Stichwort extends
nach dem ? durch super
. Ein Beispiel:
public class WildcardTest { public static void main(String[] args) { GenerischeKlasse<String> stringGeneric = new GenerischeKlasse<String>(); GenerischeKlasse<Integer> integerGeneric = new GenerischeKlasse<Integer>(); GenerischeKlasse<Object> objectGeneric = new GenerischeKlasse<Object>(); GenerischeKlasse<CharSequence> csGeneric = new GenerischeKlasse<CharSequence>(); manipulateGenClass(objectGeneric); manipulateGenClass(csGeneric); manipulateGenClass(stringGeneric); manipulateGenClass(integerGeneric); } private static void manipulateGenClass(GenerischeKlasse<? super CharSequence> gen) { // ... } }
Beim Kompilieren werden Sie feststellen, dass sich integerGeneric
nicht manipulieren lässt, da dieses Objekt rein gar nichts mit einer CharSequence
zu tun hat. Aber auch stringGeneric
lässt sich nicht manipulieren, da ein String
kein Supertyp von CharSequence
ist, sondern dieses Interface lediglich implementiert. Unser csGeneric
lässt sich hingegen problemlos manipulieren, da csGeneric
genau dem erwarteten Typen entspricht. Auch macht objectGeneric
keine Probleme – schließlich ist ein Object
die Superklasse für alle Anderen.
Wenn Sie mit Wildcards arbeiten, dann wird der generischen Typ ihrer generischen Klasse dem in der Hierarchie am höchsten stehenden Typen angepasst. Ein
? extends CharSequence
würde bspw. eine Anpassung des Typs auf eineCharSequence
bedeuten.
Bounded Type Parameters
Beschränkte Typ Parameter kann man als Gegenstück zu Wildcards bezeichnen. Mit Wildcards werden die möglichen Typen bei der Deklaration einer Variablen eingeschränkt. Bounded Type Parameters schränken die möglichen Typen schon bei der Erstellung der generischen Klasse ein. Wenn Sie eine generische Klasse programmiert haben, konnten bis jetzt alle Typen dieser Klasse zugeordnet werden. Mit den beschränkten Typ Parametern können Sie definieren, dass nur bestimmte Typen der Klasse zugeordnet werden dürfen. Dabei ist die Syntax ähnlich wie bei den Wildcards:
public class GenerischeKlasse<Generic extends Throwable> { ... }
Mit diesem Code, dürfen dieser Klasse nur generische Typen zugewiesen werden, die von Throwable
oder einer Subklasse erben, bzw. das angegebene Interface implementieren. Sollen zusätzlich noch weitere Interface implementiert werden, können diese mit &
angegeben werden.
public class GenerischeKlasse<Generic extends Throwable & CharSequence & Serializable> { ... }
Auf der nächsten Seite finden Sie ein Beispiel zur Anwendung von Wildcards und Bounded Type Parameters.