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:

  1. Beschränkung auf einen Typ und dessen Kindtypen (nach oben beschränkter Wildcard Typ)
  2. 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 eine CharSequence 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.

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.