Platzhalter (Java) - Wildcard (Java)
Der Platzhalter ? in Java ist eine spezielle Art von Typargument, das die Typensicherheit der Verwendung generischer (parametrisierter) Typen steuert . Es kann in Variablendeklarationen und -instanziierungen sowie in Methodendefinitionen verwendet werden, jedoch nicht in der Definition eines generischen Typs. Dies ist eine Form der Annotation der Varianz der Verwendungsstelle , im Gegensatz zu den Annotationen der Varianz der Definitionsstelle in C # und Scala .
Kovarianz für generische Typen
Im Gegensatz zu Arrays (die in Java kovariant sind ) sind verschiedene Instanziierungen eines generischen Typs nicht miteinander kompatibel, auch nicht explizit: Mit der Deklaration würde Generic<Supertype> superGeneric; Generic<Subtype> subGeneric; der Compiler einen Konvertierungsfehler für beide Castings (Generic<Subtype>)superGeneric und melden (Generic<Supertype>)subGeneric .
Diese Inkompatibilität kann durch den Platzhalter gemildert werden, wenn ? sie als tatsächlicher Typparameter verwendet wird: Generic<?> ist ein Supertyp aller Parameter des generischen Typs Generic . Dadurch können Objekte vom Typ Generic<Supertype> und Generic<Subtype> sicher einer Variablen oder einem Methodenparameter vom Typ zugewiesen werden Generic<?> . Die Verwendung Generic<? extends Supertype> ermöglicht dasselbe und schränkt die Kompatibilität mit Supertype und seinen untergeordneten Elementen ein. Eine andere Möglichkeit besteht darin Generic<? super Subtype> , sowohl Objekte zu akzeptieren als auch die Kompatibilität mit Subtype und allen ihren Eltern einzuschränken .
Platzhalter als Parametertyp
Im Hauptteil einer generischen Einheit wird der (formale) Typparameter wie seine Obergrenze behandelt (ausgedrückt mit extends ; Object wenn nicht eingeschränkt). Wenn der Rückgabetyp einer Methode der Typparameter ist, kann das Ergebnis (z. B. vom Typ ? ) durch eine Variable vom Typ der Obergrenze (oder Object ) referenziert werden . In der anderen Richtung passt der Platzhalter nicht zu einem anderen Typ, auch nicht Object : Wenn ? er als formaler Typparameter einer Methode angewendet wurde, können keine tatsächlichen Parameter an ihn übergeben werden. Objekte des unbekannten Typs können jedoch aus dem generischen Objekt gelesen und einer Variablen eines Supertyps der Obergrenze zugewiesen werden.
class Generic <T extends UpperBound> {
private T t;
void write(T t) {
this.t = t;
}
T read() {
return t;
}
}
...
Generic<UpperBound> concreteTypeReference = new Generic<UpperBound>();
Generic<?> wildcardReference = concreteTypeReference;
UpperBound ub = wildcardReference.read(); // Object would also be OK
wildcardReference.write(new Object()); // type error
wildcardReference.write(new UpperBound()); // type error
concreteTypeReference.write(new UpperBound()); // OK
Begrenzte Platzhalter
Ein beschränkter Platzhalter ist einer mit einer oberen oder einer unteren Vererbungsbeschränkung . Die Grenze eines Platzhalters kann entweder ein Klassentyp, ein Schnittstellentyp , ein Array-Typ oder eine Typvariable sein. Obere Schranken ausgedrückt unter Verwendung des erstreckt Schlüsselwort und unteren Grenzen der Verwendung von Super - Schlüsselwort. Platzhalter können entweder eine Obergrenze oder eine Untergrenze angeben, jedoch nicht beide.
Obergrenzen
Eine Obergrenze eines Platzhalters muss ein Subtyp der Obergrenze des entsprechenden Typparameters sein, der im entsprechenden generischen Typ deklariert ist. Ein Beispiel für einen Platzhalter, der explizit eine Obergrenze angibt, ist:
Generic<? extends SubtypeOfUpperBound> referenceConstrainedFromAbove;
Diese Referenz kann jede Parametrisierung enthalten, Generic deren Typargument ein Subtyp von ist SubtypeOfUpperBound . Ein Platzhalter, der keine Obergrenze explizit angibt, ist praktisch derselbe wie einer mit der Einschränkung extends Object , da alle Referenztypen in Java Untertypen von Object sind.
Untergrenzen
Ein Platzhalter mit einer Untergrenze, wie z
Generic<? super SubtypeOfUpperBound> referenceConstrainedFromBelow;
kann jede Parametrisierung enthalten, Generic deren beliebiges Typargument sowohl ein Subtyp der Obergrenze des entsprechenden Typparameters als auch ein Supertyp von ist SubtypeOfUpperBound .
Objekterstellung mit Platzhalter
Es dürfen keine Objekte mit einem Platzhalterargument erstellt werden. Dies ist beispielsweise new Generic<?>() verboten. In der Praxis ist dies nicht erforderlich, da, wenn ein Objekt erstellt werden soll, das einer Variablen vom Typ zugewiesen werden kann, Generic<?> einfach ein beliebiger Typ (der unter die Einschränkungen des Platzhalters fällt, falls vorhanden) als Typargument verwendet werden kann.
Dies new ArrayList<Generic<?>>() ist jedoch zulässig, da der Platzhalter kein Parameter für den instanziierten Typ ist ArrayList . Gleiches gilt für new ArrayList<List<?>>() .
In einem Array-Erstellungsausdruck muss der Komponententyp des Arrays gemäß der Definition in der Java-Sprachspezifikation, Abschnitt 4.7, überprüfbar sein. Dies bedeutet, dass, wenn der Komponententyp des Arrays Typargumente enthält, diese alle unbegrenzte Platzhalter sein müssen (Platzhalter, die nur aus a bestehen ? ). Zum Beispiel new Generic<?>[20] ist richtig, während new Generic<SomeType>[20] nicht.
In beiden Fällen ist die Verwendung von keinen Parametern eine weitere Option. Dies erzeugt eine Warnung, da sie weniger typsicher ist (siehe Rohtyp ).
Beispiel: Listen
Im Java Collections Framework List<MyClass> repräsentiert die Klasse eine geordnete Sammlung von Objekten vom Typ MyClass . Obergrenzen werden angegeben mit extends : A List<? extends MyClass> ist eine Liste von Objekten einer Unterklasse von MyClass , dh jedes Objekt in der Liste ist garantiert vom Typ MyClass , sodass man mit einer Variablen vom Typ darüber iterieren kann MyClass
public void doSomething(List<? extends MyClass> list) {
for (MyClass object : list) { // OK
// do something
}
}
Es kann jedoch nicht garantiert werden, dass MyClass dieser Liste ein Objekt vom Typ hinzugefügt werden kann :
public void doSomething(List<? extends MyClass> list) {
MyClass m = new MyClass();
list.add(m); // Compile error
}
Das Umgekehrte gilt für Untergrenzen, die wie folgt angegeben werden super : A List<? super MyClass> ist eine Liste von Objekten einer Oberklasse von MyClass , dh die Liste kann garantiert jedes Objekt vom Typ enthalten MyClass , sodass jedes Objekt vom Typ hinzugefügt werden kann MyClass :
public void doSomething(List<? super MyClass> list) {
MyClass m = new MyClass();
list.add(m); // OK
}
Es kann jedoch nicht garantiert werden, dass diese Liste mit einer Variablen vom Typ iteriert werden kann MyClass :
public void doSomething(List<? super MyClass> list) {
for (MyClass object : list) { // Compile error
// do something
}
}
Um sowohl Objekte vom Typ MyClass zur Liste hinzufügen als auch mit einer Variablen vom Typ iterieren zu können MyClass , List<MyClass> wird a benötigt. Dies ist der einzige Typ, der List sowohl List<? extends MyClass> als als auch ist List<? super MyClass> .
Die Mnemonik PECS (Producer Extends, Consumer Super) aus dem Buch Effective Java von Joshua Bloch bietet eine einfache Möglichkeit, sich daran zu erinnern, wann Platzhalter (entsprechend Covarianz und Kontravarianz) in Java verwendet werden müssen.
Siehe auch
- Begrenzte Quantifizierung
- Kovarianz und Kontravarianz (Informatik)
- Generika im Java # -Typ-Platzhalterabschnitt erläutert die unteren und oberen Platzhaltergrenzen
Verweise
- Die Java-Sprachspezifikation, dritte Ausgabe (Sun), ISBN 978-0-321-24678-3 http://java.sun.com/docs/books/jls/
- Java-Tutorials, Lektionsgenerika http://download.oracle.com/javase/tutorial/java/generics/index.html
- Erfassen von Platzhaltern, http://bayou.io/draft/Capturing_Wildcards.html
- Typkompatibilität in Java http://public.beuth-hochschule.de/~solymosi/veroeff/typkompatibilitaet/Typkompatibilitaet.html#Joker (in deutscher Sprache)