Was bedeuten die Ausdrücke ? super T, ? extends T und ? in Java?
Bei dem hier genannten ‚?’ handelt es sich um sogenannte Wildcards. Diese Wildcard repräsentieren einen unbekannten Typ, daher kann man es auch mit einem Platzhalter vergleichen. Im Gegensatz zu einem Typ-Parameter wie ‚<T>’, können Wildcards nicht als Platzhalter für Typ-Parameter weiter genutzt werden. Wildcards sind ein Laufzeit-Konstrukt, während Generic-Types (‚T‘) vom Compiler bereits geprüft werden können. Mittels Wildcards verallgemeinert man sozusagen den zu verwendeten Typ, während man mit ‚<T>’ sich explizit auf den übergebenen Typ festlegt.
Java Wildcard-Arten
Es gibt drei verschiedenen Möglichkeiten ein Wildcard zu definieren: „Lower Bound“, „Upper Bound“ und „Unbounded“.
Upper Bound
Mittels „extends“ definiert man einen „Upper Bound“. Dieser wird genutzt, wenn man beispielsweise eine Collection von sämtlichen Zahlentypen verwenden möchte. Demnach kann man den Diamant-Operator ‚? extends Number’ schreiben. Dies macht deutlich, dass die folgende Collection nicht nur Integer-Typen aufnehmen kann, sondern auch Objekte vom Typ Double usw.
List<? extends Number> myList = null; myList = new new ArrayList<Integer>(); myList = new new ArrayList<Double>(); myList = new new ArrayList<Number>(); myList = new new ArrayList<String>(); //ungültig!!
Der letzte Ausdruck ist ungültig, da myList nur Listen vom Untertyp Number akzeptiert.
Lower Bound
Mittels „super“ definiert man einen „Lower Bound“. Damit kann man beispielsweise ‚? super A’ schreiben, um die erlaubten Typen auf alle Untertypen von A (A mit eingeschlossen) zu beschränken. So kann man beispielsweise folgendes Konstrukt verwenden, um die erlaubten Typen auf ausschließlich RuntimeExceptions und Unterklassen von RuntimeExceptions zu beschränken
List<? super RuntimeException> myList = new ArrayList<>(); myList.add(new RuntimeException()); myList.add(new NullPointerException()); myList.add(new IOException()); //ungültig!!
Letzteres führt zu einem Fehler, da IOException nicht vom Typ RuntimeException erbt, sondern vom Typ Exception. RuntimeException erbt zwar auch von Exception, aber durch die obige Deklaration haben wir ausschließlich Typen erlaubt, die von RuntimeException erben.
Durch die oben genannten Ausdrücke kann man so einen Gültigkeitsbereich von erlaubten Typen für generische Klassen (Generics) oder Methoden deklarieren, um beispielsweise eine Liste für Nummern vor einem „Missbrauch“ mit Strings zu bewahren. (Siehe Beispiel oben)
Grob gesagt: Der Unterschied zwischen ‚super’ und ‚extends’ besteht darin, in welcher Ableitungs-Hierarchie ein Typ stehen darf.