Weitere Informationen finden Sie in diesen FAQ:
http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1
Hier in Kurzform einige wichtige Punkte aus dieser FAQ.
Beispiel (ein generischer Typ):
Klassenpaar<X,Y> {
privates X zuerst;
privat Y Sekunde; öffentliches Paar(X a1, Y a2) {
zuerst = a1;
Sekunde = a2;
}
public X getFirst() { return first; }
public Y getSecond() { return second; }
public void setFirst(X arg) { first = arg; }
public void setSecond(Y arg) { second = arg; }
}
Die Klasse Paar hat zwei Typparameter X Und Y . Beim generischen Typ werden sie durch Typargumente ersetzt Paar wird instanziiert. Zum Beispiel in der Erklärung Paar<String, Datum> der Typparameter X wird durch das Typargument ersetzt Zeichenfolge Und Y wird ersetzt durch Datum .
Der Umfang der Bezeichner X Und Y Ist die gesamte Definition der Klasse. In diesem Bereich die beiden Typparameter X Und Y werden wie Typen verwendet (mit einigen Einschränkungen). Im obigen Beispiel werden die Typparameter als Argument und Rückgabetyp von Instanzmethoden und als Typen von Instanzfeldern verwendet.
Typparameter können mit Grenzen deklariert werden. Grenzen der Möglichkeit des Zugriffs auf Methoden des unbekannten Typs, für den der Typparameter steht. In unserem Beispiel rufen wir keine Methoden unbekannter Typen auf X Und Y . Aus diesem Grund sind die beiden Typparameter unbegrenzt.
Beispiel (eines konkret parametrisierten Typs):
public void printPair( Paar<String,Long> Paar) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Paar<String,Long> limit = neu Paar<String,Long> („maximal“,1024L);
printPair(limit);
Beispiel (für einen mit Platzhaltern parametrisierten Typ):
public void printPair( Paar<?,?> Paar) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Paar<?,?> limit = neu Paar<String,Long> („maximal“,1024L);
printPair(limit);
Alle Instanziierungen eines generischen Typs verwenden dieselbe Laufzeittypdarstellung, nämlich die Darstellung des Rohtyps. Zum Beispiel die Instanziierungen eines generischen Typs Liste , wie zum Beispiel List<Date> , List<String> , List<Long> usw. haben zur Kompilierungszeit unterschiedliche statische Typen, aber denselben dynamischen Typ Liste zur Laufzeit.
Eine Besetzung besteht aus zwei Teilen:
- eine vom Compiler zur Kompilierungszeit durchgeführte statische Typprüfung und
- eine dynamische Typprüfung, die von der virtuellen Maschine zur Laufzeit durchgeführt wird.
Der statische Teil sortiert unsinnige Casts aus, die nicht erfolgreich sein können, wie zum Beispiel der Cast from Zeichenfolge Das Datum oder von List<String> Das List<Date> .
Der Teil dynamisch nutzt die Laufzeittypinformationen und führt zur Laufzeit eine Typprüfung durch. Es erhöht a ClassCastException wenn der dynamische Typ des Objekts nicht der Zieltyp (oder ein Untertyp des Zieltyps) der Umwandlung ist. Beispiele für Besetzungen mit dynamischem Anteil sind die Besetzung von Objekt Das Zeichenfolge oder von Objekt Das List<String> . Das sind die sogenannten Downcasts, von einem Supertyp bis hin zu einem Subtyp.
Beispiele für konkrete parametrisierte Typen sind List<String> , Map<String,Date> , aber nicht List<? erweitert Number> oder Map<String,?> .
Manchmal wird erwartet, dass a List<Object> wäre ein Supertyp von a List<String> , Weil Objekt ist ein Supertyp von Zeichenfolge . Diese Erwartung ergibt sich aus der Tatsache, dass für Arrays eine solche Art von Beziehung existiert: Objekt[] ist ein Supertyp von String[] , Weil Objekt ist ein Supertyp von Zeichenfolge . (Diese Art von Beziehung ist bekannt als Kovarianz .) Die Super-Subtyp-Beziehung der Komponententypen erstreckt sich bis in die entsprechenden Array-Typen. Für Instanziierungen generischer Typen existiert keine solche Typbeziehung. (Parametrierte Typen sind nicht Covariante.)
Das Fehlen einer Super-Subtyp-Beziehung zwischen Instanziierungen desselben generischen Typs hat verschiedene Konsektionen. Hier ist ein Beispiel.
Beispiel:
void printAll(ArrayList<Object> c) {
for (Objekt o : c)
System.out.println(o);
} ArrayList<String> list = new ArrayList<String>();
... Liste ausfüllen ...
printAll(list); // Fehler
A ArrayList<String> Das Objekt kann nicht als Argument an eine Methode übergeben werden, die nach einem fragt ArrayList<Object> weil die beiden Typen Instanziierungen desselben generischen Typs sind, aber für anders Typargumente und aus diesem Grund sind sie nicht miteinander kompatibel.
Konkrete parametrisierte Typen sind konkrete Instanziierungen eines generischen Typs. Sie sind schnell wie Typen; Es gibt nur weinige Einschränkungen. Sie dürfen NICHT für folgende Zwecke verwendet werden:
- zur Erstellung von Arrays
- in der Ausnahmebehandlung
- in einem Klassenliteral
- in einem Instanz von Ausdruck
Arrays, die Elemente enthalten, deren Typ ein konvertierter parametrisierter Typ ist, sind unzulässig.
Beispiel (für einen unzulässigen Array-Typ):
static void test() {
Paar<Integer,Integer>[] intPairArr = neu Paar<Integer,Integer>[10] ; // Fehler
addElements(intPairArr);
Pair<Integer,Integer> pair = intPairArr[1];
Ganzzahl i = pair.getFirst();
pair.setSecond(i);
}
static void addElements( Objekt[] objArr) {
objArr[0] = neues Pair<Integer,Integer>(0,0);
objArr[1] = neues Pair<String,String>("",""); // sollte mit ArrayStoreException fehlschlagen
}
Der Compiler verbietet die Erstellung von Arrays, deren Komponententyp ein konkreter parametrisierter Typ ist, z Paar<Integer,Integer> in unserem Beispiel. Wir haben im vorherigen Eintrag besprochen, warum es sinnvoll ist, dass der Compiler a qualifiziert Paar<Integer,Integer>[] als illegal. Das Hauptproblem besteht darin, dass der Compiler und das Laufzeitsystem sicherstellen müssen, dass ein Array ein ist homogen Folge von Elementen des gleichen Typs. Eine der Typprüfungen, nämlich die von der virtuellen Maschine zur Laufzeit durchgeführte Array-Store-Prüfung, erkennt das fehlerhafte Einfügen eines fremden Elements nicht. Im Beispiel die zweite Einfügung im addElements Die Methode sollte fehlschlagen, da ein Zeichenfolgenpaar zu einem Array ganzahliger Werte hinzugefügt wurde, aber sie schlägt nicht wie erwartet fehl. Die Gründe wurden im vorherigen Eintrag besprochen.
Wenn wir keine Arrays verwenden können, die Elemente enthalten, deren Typ ein konvertierter parametrisierter Typ ist, was können wir dann als Workaround verwenden?
Betrachten wir drei denkbare Workarounds:
- Array von Rohtypen
- Array eines unbegrenzten, mit Platzhaltern parametrisierten Typs
- Sammlung statt Array
Während Arrays konkreter parametrisierter Typen unzulässig sind, sind Sammlungen konkreter parametrisierter Typen zulässig.
Ein parametrisierter Platzhaltertyp ist eine Instanziierung eines generischen Typs, bei dem mindesten ein Typargument ein Platzhalter ist. Beispiele für mit Platzhaltern parametrisierte Typen sind: Sammlung<?> , List<? erweitert Number> , Komparator<? super String> Und Paar<String,?> . Ein mit Platzhalterzeichen parametrisierter Typ bezeichnet eine Typfamilie, die konkrete Instanziierungen eines generischen Typs umfasst. Die Art des verwendeten Platzhalters bestimmt, welche konkreten parametrisierten Typen zur Familie gehören. Zum Beispiel der mit Platzhaltern parametrisierte Typ Sammlung<?> bezeichnet die Familie aller Instanziierungen der Sammlung Schnittstelle unabhängig vom Typargument. Der mit Platzhaltern parametrisierte Typ List<? erweitert Number> bezeichnet die Familie aller Listentypen, bei denen der Elementtyp ein Untertyp von ist Nummer . Der mit Platzhaltern parametrisierte Typ Komparator<? super String> ist die Familie aller Instanziierungen der Komparator Schnittstelle für Typargumenttypen, die Supertypen von sind Zeichenfolge.
Ein mit Platzhaltern parametrisierter Typ ist kein Typ im regulären Sinne (anders als eine nicht parametrisierte Klasse/Schnittstelle oder ein Rohtyp).
Für die Typisierung können parametrisierte Wildcard-Typen verwendet werden (wie nicht parametrisierte Klassen und Schnittstellen):
- als Argument- und Rückgabetypen von Methoden
- als Typ eines Feldes oder einer lokalen Referenzvariablen
- als Komponententyp eines Arrays, als Typargument anderer parametrisierter Typen
- als Zieltyp in Casts
Nicht nur Typen können generisch sein, sondern auch Methoden. Statische und nicht statische Methoden sowjetischer Konstruktoren können Typparameter haben. Die Syntax zur Deklaration der formalen Typparameter ähnelt der Syntax für generische Typen. Der Typparameterabschnitt wird durch spitze Klammern beligt und erscheint vor dem Rückgabetyp der Methode. Seine Syntax und Bedeutung ist identisch mit der Typparameterliste eines generischen Typs.
Hier ist das Beispiel eines Genericums max Methode, die den größten Wert in einer Sammlung von Elementen eines unbekannten Typs berechnet A .
Beispiel (eine generische Methode):
Klassensammlungen {
öffentliche statische Aufladung <A erweitert Comparable<A>> A max(Collection< A > xs) {
Iterator< A > xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
Der max Die Methode verfügt über einen Typparameter mit dem Namen A . Es handelt sich um einen Platzhalter für den Elementtyp der Sammlung, mit der die Methode arbeitet. Der Typparameter hat eine Grenze; Es muss ein Typ sein, der ein Untertyp von ist Vergleichbar<A>
Beispiel (diesmal vom gleichen generischen Typ). mit Grenzen):
öffentliche Schnittstelle Comparable<T> {
public int CompareTo(T arg);
}
öffentliche Klasse TreeMap<Key erweitert Comparable<Key> ,Data>{
private statische Klasse Entry<K,V> {
K-Taste;
V-Wert;
Eintrag<K,V> left = null;
Eintrag<K,V> right = null;
Eintrag<K,V> übergeordnetes Element;
}
privater transienter Eintrag<Key,Data> root = null;
...
private Entry<Key,Data> getEntry(Key key) {
Entry<Key,Data> p = root;
Schlüssel k = Schlüssel;
while (p != null) {
int cmp = k. CompareTo(p.key) ;
if (cmp == 0)
Rückkehr p;
sonst wenn (cmp < 0)
p = p.links;
anders
p = p.rechts;
}
null zurückgeben;
}
öffentlicher boolescher Wert enthältSchlüssel(Schlüsselschlüssel) {
return getEntry(key) != null;
}
...
}
Im obigen Beispiel der Typparameter Schlüssel hat die Grenze Vergleichbar<Key> . Die Angabe einer Grenze hat zwei Auswirkungen:
- Es ermöglicht den Zugriff auf die Methoden, die die Grenze angibt . Im Beispiel die Grenze Vergleichbar<Key> ermöglicht den Zugriff auf die vergleichenMit Methode, die wir bei der Implementierung unserer aufrufen möchten TreeMap Klasse.
- Für die Instanziierung des generischen Typs können nur Typen „innerhalb der Grenzen“ verwendet werden. Im Beispiel ist ein parametrisierter Typ wie z TreeMap<Number,String> würde abgelehnt werden, weil der Typ Nummer ist kein Subtyp von Vergleichbar<Number> . Ein parametrisierter Typ wie TreeMap<String,String> würde akzeptiert, da der Typ String innerhalb der Grenzen liegt, d. h. ein Subtyp von ist Vergleichbar<String> .
Alle Klassen, Schnittstelten und Aufzählungstypen können als Typparameterbindung verwendet werden, einschließlich verschachtelter und innerer Typen. Weder primitive Typen noch Array-Typen können als Typparameterbindung verwendet werden.
Beispiele (für Typparametergrenzen):
Klasse X0 <T erweitert int > { ... } // Fehler
Klasse X1 <T erweitert Objekt[] > { ... } // Fehler
Klasse X2 <T erweitert Nummer > { ... }
Klasse X3 <T erweitert Zeichenfolge > { ... }
Klasse X4 <T erweitert Lauffähig > { ... }
Klasse X5 <T erweitert Thread.State > { ... }
Klasse X6 <T erweitert Liste > { ... }
Klasse X7 <T erweitert List<String> > { ... }
Klasse X8 <T erweitert List<? erweitert Number> > { ... }
Klasse X9 <T erweitert Vergleichbar<? super Number> > { ... }
Klasse X10<T erweitert Map.Entry<?,?> > { ... }
Das Codebeispiel zeigt, dass primitive Typen wie z int und Array-Typen wie Objekt[] sind als Typparameterbindung nicht zulässig.
Klassentypen, wie z Nummer oder Zeichenfolge , und Schnittstellentypen, wie z Lauffähig , sind als Typparameterbindung zulässig.
Aufzählungstypen, wie z Thread.State sind auch als Typparameterbindung zulässig. Thread.State ist ein Beispiel für einen verschachtelten Typ, der als Typparameterbindung verwendet wird. Auch nichtstatische Innentypen sind zulässig.
Rohtypen sind als Typparameterbindung zulässig; Liste ist ein Beispiel.
Parametrisierte Typen sind als Typparameterbindung zulässig, auch konkret parametrisierter Typen wie z List<String> , begrenzte Wildcard-parametrisierte Typen wie z List<? erweitert Number> Und Vergleichbar<? super Long> und unbegrenzte, mit Platzhaltern parametrisierte Typen wie z Map.Entry<?,?> . Eine Grenze, bei der es sich um einen parametrisierten Platzhaltertyp handelt, lässt als Typargument alle Typen zu, die zu der Typfamilie gehören, die der Platzhalter bezeichnet. Die durch Platzhalter parametrisierte Typbindung gerätt nur eschreichten Zugrichten auf Felder und Methoden. Die Einschränkungen hängen von der Art des Platzhalters ab.
Beispiel (für einen mit Platzhalterzeichen parametrisierten Typ als Typparametergrenze):
Klasse X< T erweitert List<? erweitert Number> > {
public void someMethod(T t) {
t.add(new Long(0L)); // Fehler
Zahl n = t.remove(0);
}
}
Klassentest {
public static void main(String[] args) {
X<ArrayList< Lang >> x1 = new X<ArrayList<Long>>();
X<ArrayList< Zeichenfolge >> x2 = new X<ArrayList<String>>(); // Fehler
}
}
Referenzvariablen vom Typ T (der Typparameter) werden wie Referenzvariablen eines Wildcard-Typs (der Typparameter gebunden) behandelt. In unserem Beispiel hat dies zur Folge, dass der Compiler den Aufruf von Methoden ablehnt, die ein Argument des „unbekannten“ Typs annehmen, für den der Typparameter steht, wie z List.add , da es sich bei der Grenze um einen mit Platzhaltern parametrisierten Typ mit einer Obergrenze handelt.
Gleichzeitig die Grenze List<? erweitert Number> bestimmt die Typen, die als Typargumente verwendet werden können. Der Compiler akzeptiert alle Typargumente, die zur Typfamilie gehören List<? erweitert Number> , also alle Untertypen von Liste mit einem Typargument, das ein Subtyp von ist Nummer .
Ein Typparameter kann als Grenze eines anderen Typparameters verwendet werden.
Beispiel (für einen Typparameter, der als Typparametergrenze verwendet wird):
Klasse Triple <T> {
privates T fst, snd, trd;
öffentlich < U erweitert T , V erweitert T , W erweitert T > Triple(U arg1, V arg2, W arg3) {
fst = arg1;
snd = arg2;
trd = arg3;
}
}
In diesem Beispiel der Typparameter T der parametrisierten Klasse wird als Grenze der Typparameter verwendet U , V Und W einer parametrisierten Instanzmethode dieser Klasse.
Weitere Möglichkeiten zur Verwendung von Typparametern als Grenzen anderer Typparameter umfassen Situationen, in denen ein verschachtelter Typ innerhalb eines genericischen Typs oder eine locale Klasse innerhalb einer generischen Methode definiert wird. Es ist sogar zulässig, einen Typparameter als Grenze eines anderen Typparameters im selben Typparameterabschnitt zu verwenden.
Eine Grenze, die eine Klasse ist, gerätt Zugriff auf alle ihre öffentlichen Mitglieder, also auf öffentliche Felder, Methoden und verschachtelte Typen. Nur Konstruktoren werden nicht hergestellt, da es keine Garantie dafür gibt, dass eine Unterklasse der Bindung denselben Konstruktoren wie die Bindung hat.
Beispiel (einer Klasse, die als Grenze eines Typparameters verwendet wird):
öffentliche Klasse Superklasse { // statische Mitglieder
öffentliche Aufzählung EnumType {THIS, Those}
öffentliches statisches Objekt staticField;
public static void staticMethod() { ... }// nicht statische Mitglieder
öffentliche Klasse InnerClass { ... }
öffentliches Objekt nonStaticField;
public void nonStaticMethod() { ... }// Konstruktoren
public SuperClass() { ... }// private Mitglieder
privates Objekt privateField;...
}öffentliche Abschlussklasse SomeClass<T erweitert Superklasse > {
privates T-Objekt;
public SomeClass(T t) { object = t; }öffentlicher String toString() {
zurückkehren
„statischer verschachtelter Typ : „+T.EnumType.class+“\n“
+"statisches Feld : "+T.staticField+"\n"
+"statische Methode : "+T.staticMethod()+"\n"
+"nicht statischer verschachtelter Typ: "+T.InnerClass.class+"\n"
+"non-static field : "+object.nonStaticField+"\n"
+"nicht-statische Methode : "+object.nonStaticMethod()+"\n"
+"constructor : "+(new T())+"\n" // Fehler
+"private member : "+object.privateField+"\n" // Fehler
;
}
}
Die Grenze Superklasse gewährt Zugriff auf seine verschachtelten Typen, statischen Felder und Methoden sowie nicht statische Felder und Methoden. Nur der Konstruktor ist nicht zugänglich. Dies liegt daran, dass Konstruktoren nicht vererbt werden. Jede Unterklasse definiert ihre eigenen Konstruktoren und muss die Konstruktoren ihrer Oberklasse nicht unterstützen. Daher gibt es keine Garantie dafür, dass eine Unterklasse von Superklasse Wird derselbe Konstruktor wie seine Oberklasse sein.
Der Kontext, in dem Enum<E erweitert Enum<E>> ” erscheint, ist die Erklärung des Aufzählung Klasse im Paket java.lang :
öffentliche abstrakte Klasse Enum<E erweitert Enum<E>> {
...
}
Der Typ Aufzählung ist die gemeinsame Basisklasse aller Aufzählungstypen. In Java ein Aufzählungstyp wie Farbe wird in eine Klasse übersetzt Farbe das erstreckt sich Enum<Color> . Der Zweck der Superklasse Aufzählung besteht darin, Funktionen bereitzustellen, die alle Aufzählungstypen gemeinsam sind.
Hier ist eine Skizze der Klasse Aufzählung :
öffentliche abstrakte Klasse Enum< E erweitert Enum<E>> implementiert Comparable< E >, Serialisierbar {
privater endgültiger String-Name;
public final String name() { ... } private final int ordinal;
public final int ordinal() { ... }protected Enum(String name, int ordinal) { ... }
öffentlicher String toString() { ... }
public final boolean equals(Object other) { ... }
public final int hashCode() { ... }
protected final Object clone() löst CloneNotSupportedException { ... } aus
public final int CompareTo( E o) { ... }öffentliches Finale Class< E > getDeclaringClass() { ... }
public static <T erweitert Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}
Das Überraschende in der Erklärung „ Enum<E erweitert Enum<E>> ” ist die Tatsache, dass die neu definierte Klasse Aufzählung und sein neu definierter Typparameter E erscheinen in der Grenze desselben Typparameters. Es bedeutet, dass die Aufzählung Der Typ muss für einen seiner Untertypen instanziiert werden. Um zu verstehen, warum dies sinnvoll ist, bedenken Sie, dass jeder Aufzählungstyp in einen Untertyp von übersetzt wird Aufzählung .
Hier ist der erfundene Aufzählungstyp Farbe :
Aufzählung Farbe {ROT, BLAU, GRÜN}
Der Compiler übersetzt es in die folgende Klasse:
öffentliche Abschlussklasse Farbe erstreckt sich Enum<Color> {
public static final Color[] Values() { return (Color[])$VALUES.clone(); }
öffentlicher statischer FarbwertOf(String name) { ... } private Color(String s, int i) { super(s, i); }öffentliche statische endgültige Farbe ROT;
öffentliche statische endgültige Farbe BLAU;
öffentliche statische endgültige Farbe GRÜN;private statische endgültige Farbe $VALUES[];
statisch {
ROT = neue Farbe("RED", 0);
BLAU = neue Farbe("BLAU", 1);
GRÜN = neue Farbe("GRÜN", 2);
$VALUES = (neue Farbe[] { ROT, BLAU, GRÜN });
}
}
Die Vererbung bewirkt, dass die Farbe Typ erbt alle in implementierten Methoden Enum<Color> . Unter ihnen ist die vergleichenMit Verfahren. Der Farbe. vergleichenMit Methode sollte wahrscheinlich eine dauern Farbe als Argument. Um dies zu ermöglichen, Klasse Aufzählung ist generisch und die Enum.compareTo Methode dauert Aufzählung Typparameter E als Argument. Geben Sie als Ergebnis ein Farbe abgeleitet von Enum<Color> erbt a vergleichenMit Methode, die eine dauert Farbe as und Argument, genau so, wie es sollte.
Wenn wir die Erklärung analysieren: Enum<E erweitert Enum<E>> „Wir können sehen, dass dieses Muster mehrere Aspekte hat.“
Erstens gibt es die Tatsache, dass der Typparameter, der gebunden wird, der Typ selbst ist: „ Aufzählung <E erweitert Aufzählung <E>> “. Es stellt sicher, dass nur Untertypen des Typs vorhanden sind Aufzählung sind als Typargumente zulässig. (Theoretisch, Typ Aufzählung könnte auf sich selbst instanziiert werden, wie in Enum<Enum> )
Zweitens gibt es die Tatsache, dass der Parameter-gebundene Typ der parametrisierte Typ ist Aufzählung <E> , das den Typparameter verwendet E als Typargument der Grenze. Diese Deklaration stellt sicher, dass die Vererbungsbeziehung zwischen einem Subtyp und einer Instanziierung von Aufzählung ist von der Form X erweitert Enum<X> “. Ein Untertyp wie " X erweitert Enum<Y> ” kann aufgrund von Typargumenten nicht deklariert werden Y würde nicht innerhalb der Grenzen liegen; nur Unterarten von Enum<X> liegen im Rahmen.
Drittens gibt es die Tatsache, dass Aufzählung ist in erster Linie generisch. Dies bedeutet, dass einige Methoden der Klasse Aufzählung Nehmen Sie ein Argument an oder geben Sie einen Wert eines unbekannten Typs zurück (oder hängen Sie auf andere Weise von einem unbekannten Typ ab). Wie wir bereits wissen, wird dieser unbekannte Typ später ein Untertyp sein X von Enum<X> . Daher im parametrisierten Typ Enum<X> , diese Methoden beinhalten den Untertyp X , und sie werden in den Untertyp vererbt X . Der vergleichenMit Methode ist ein Beispiel für eine solche Methode; Es wird von der Oberklasse in jede Unterklasse übertragen und verfügt über eine unterklassenspezifische Signatur.
Um es zusammenzufassen: Die Erklärung Enum<E erweitert Enum<E>> „kann entziffert werden als: Aufzählung ist ein generischer Typ, der nur für seine Untertypen instanziiert werden kann, und diese Untertypen erben einige nützliche Methoden, von denen einige untertypspezifische Argumente annehmen (oder auf andere Weise vom Untertyp abhängen).
Typparameter der Untergrenze von Klassen
Typparameter können mehrere Grenzen haben, wie in Klasse Box<T erweitert Number> {...} . Ein Typparameter kann jedoch keine untere Grenze haben, d. h. ein Konstrukt wie Klasse Box<T Großartig Nummer> {...} ist nicht gestatet. Warum nicht? Die Antwort lautet: Es ist sinnlos, weil man damit nichts kaufen würde, wenn es erlaubt wäre. Lassen Sie uns sehen, warum die Untergrenze von Typparametern von Klassen verwirrend ist, indem wir unschenker, was eine Obergrenze für einen Typparameter bedeutet.
Die Obergrenze für einen Typparameter hat drei Auswirkungen:
- Eingeschränkte Instanziierung. Die Obergrenze schränkt die Menge der Typen ein, die zur Instanziierung des generischen Typs verwendet werden können. Wenn wir a erklären Klasse Box<T erweitert Number> {...} dann würde der Compiler sicherstellen, dass nur Untertypen von Nummer kann als Typargument verwendet werden. Das heißt, a Box<Number> oder ein Box<Long> ist erlaubt, aber a Box<Object> oder Box<String> würde abgelehnt werden
Beispiel (für eine eingeschränkte Instanziierung aufgrund einer Obergrenze für einen Typparameter):
Klasse Box<T erweitert Number> {
privater T-Wert;
public Box(T t) { value = t; }
...
} Klassentest {
public static void main(String[] args) {
Box<Long> boxOfLong = neue Box<Long>(0L); // Bußgeld
Box<String> boxOfString = new Box<String>(""); // Fehler: String liegt nicht innerhalb der Grenzen
}
}
- Zugriff auf nicht-statische Mitglieder. Die Obergrenze ermöglicht den Zugriff auf alle öffentlichen nicht statischen Methoden und Felder der Obergrenze. Bei der Umsetzung unserer Klasse Box<T erweitert Number> {...} Wir können alle in der Klasse definierten öffentlichen nicht statischen Methoden aufrufen Nummer , wie zum Beispiel intValue() zum Beispiel. Ohne die Obergrenze würde der Compiler jeden solchen Aufruf ablehnen. Beispiel (für den Zugriff auf nicht statische Mitglieder aufgrund einer Obergrenze für einen Typparameter):
Klasse Box<T erweitert Number> {
privater T-Wert;
public Box(T t) { value = t; }
public int increment() { return value.intValue()+1; } // <= wäre ein Fehler ohne die Zahl gebunden
...
}
- Geben Sie „Löschen“ ein. Die Obergrenze ganz links wird zum Löschen des Typs verwendet und ersetzt den Typparameter im Bytecode. In unserem Klasse Box<T erweitert Number> {...} alle Vorkommen von T würde durch die Obergrenze ersetzt werden Nummer . Zum Beispiel, wenn Klasse Kasten hat ein privates Feld vom Typ T und eine Methode Leere Menge (T-Inhalt) zum Festlegen dieses privaten Felds wäre das Feld vom Typ Nummer nach der Typlöschung würde die Methode in eine Methode übersetzt void set(Zahleninhalt) .
Beispiel (für die Verwendung einer Obergrenze für einen Typparameter bei der Typlöschung – vor Typ Löschung):
Klasse Box< T erweitert Zahl > {
Privat T Wert;
öffentliche Box( T t) { Wert = t; }
...
}
- Beispiel (für die Verwendung einer Obergrenze für einen Typparameter bei der Typlöschung –
nach
- Typ Löschung):
Klasse Box {
Privat Nummer Wert;
öffentliche Box( Nummer t) { Wert = t; }
...
}
- Darüber hinaus erscheint die Obergrenze ganz links an weiteren Stellen, beispielsweise bei automatisch eingefügten Casts und Bridge-Methoden.
Welche Nebenwirkungen hätten oder sollten sie haben, wenn Untergrenzen für Typparameter zulässig wären? Wenn ein Konstrukt wie Klasse Box<T Großartig Nummer> {...} erlaubt wären, was würde das bedeuten? Was würden die drei Nebenwirkungen einer oberen Typparametergrenze – eingeschränkte Instanziierung, Zugriff auf nicht-statische Member, Typlöschung – für eine untere Grenze bedeuten?
Eingeschränkte Instanziierungen. Der Compiler könnte die Menge der Typen, die für die Instanziierung des generischen Typs verwendet werden können, mit einem Typparameter mit unterer Grenze einschränken. Der Compiler könnte beispielsweise Instanziierungen zulassen wie Box<Number> Und Box<Object> von a Box<T Großartig Nummer> und lehnen Sie Instanziierungen wie ab Box<Long> oder Box<Short> . Dies wäre ein Effekt, der mit dem restriktiven Nebeneffekt übereinstimmt, der für obere Typparametergrenzen beschrieben wurde.
Zugriff auf nicht-statische Mitglieder. Eine niedrigere Typparametergrenze gewährt keinen Zugriff auf bestimmte Methoden, die über die von der Klasse geerbten Methoden hinausgehen Objekt . Am Beispiel von Box<T Großartig Nummer> die Supertypen von Nummer haben nichts gemeinsam, außer dass sie Referenztypen und daher Untertypen von sind Objekt . Der Compiler kann nicht davon ausgehen, dass das Feld vom Typ ist T ist vom Typ Nummer oder eine Unterart davon. Stattdessen das Feld vom Typ T kann von jedem Supertyp sein Nummer , wie zum Beispiel Serialisierbar oder Objekt . Der Aufruf einer Methode wie z intValue() ist nicht mehr typsicher und der Compiler müsste es ablehnen. Infolgedessen würde die untere Typparametergrenze keinen Zugriff auf nicht statische Mitglieder ermöglichen, die über die in der Klasse definierten hinausgehen Objekt und hat somit die gleiche Wirkung wie „nobound“.
Geben Sie „Löschen“ ein . Nach dieser Logik macht es keinen Sinn, die Vorkommen des Typparameters durch seine untere Grenze ganz links zu ersetzen. Deklarieren einer Methode wie des Konstruktors Kasten( T T) als Konstrukteur Kasten( Nummer T) macht in Anbetracht dessen keinen Sinn T wird durch einen Supertyp von ersetzt Nummer . Ein Objekt könnte in einer Instanziierung korrekt an den Konstruktor übergeben werden Box<Object> und der Konstruktor würde es ablehnen. Das wäre absolut falsch. Die Typlöschung würde also alle Vorkommen der Typvariablen ersetzen T nach Typ Objekt und nicht durch seine Untergrenze. Auch hier hätte die Untergrenze den gleichen Effekt wie „keine Grenze“.
Möchten Sie herausfinden, was es bedeuten würde, wenn beide nieger wären? _Und_ Obergrenzen waren erlaubt? Persönlich möchte ich gar nicht darüber nachdenken und würde es lieber unter „nicht beherrschbar“ einordnen, wenn Sie es erlauben.
Die Quintessenz ist: Das ist alles ein Großartig „bound would buy you“ ist die Einschränkung, die nur Supertypen davon haben Nummer können als Typargumente verwendet werden. Und selbst das wird häufig missverstanden. Das würde es NICHT bedeuten Klasse Box<T super Number> {...} enthält nur Instanzen von Supertypen von Nummer . Ganz im Gegenteil – wie das folgende Beispiel zeigt!
Beispiel (für die Verwendung einer Obergrenze für einen Typparameter bei der Typlöschung – vor Typ Löschung):
Klasse Box< T-Super-Nummer > {
Privat T Wert;
öffentliche Box( T t) { Wert = t; }
...
}
Beispiel (für die Verwendung einer Obergrenze für einen Typparameter bei der Typlöschung – nach Typ Löschung):
Klasse Box {
Privat Objekt Wert;
öffentliche Box( Objekt t) { Wert = t; }
...
}
A Klasse Box<T Großartig Nummer> {...} würde durch Typlöschung in a übersetzt werden Kasten enthält eine Objekt Feld und sein Konstruktor würden übersetzt werden Box(Objekt t) . Das unterscheidet sich grundlegend von a Klasse Box<T erstreckt sich Nummer> {...} , was übersetzt werden würde zu a Kasten enthält a Nummer Feld und sein Konstruktor würden übersetzt werden Box (Anzahl t) . Folglich a Box<Number> instanziiert von a Klasse Box<T erweitert Number> {...} wäre anders als a Box<Number> Instanziierung von a Klasse Box<T super Number> {...} , was wahrscheinlich Verwirrung stiften wird. Aus diesem Grund sind Untergrenzen für Typparameter von Klassen nicht sinnvoll.
Typparameter der Untergrenze von Methoden
Im Zusammenhang mit Methoden und ihren Argumenttypen kann gelegentlich ein Typparameter mit einer unteren Grenze nützlich sein.
Beispiel (für eine Methode, die von einem Typparameter mit einer Untergrenze profiterien würde):
Klassenpaar<X,Y> {
privates X zuerst;
privat Y Sekunde;
...
öffentlich < Ein super X , B super Y > B addToMap(Map<A,B> map) { // Fehler: Typparameter darf keine untere Grenze haben
return map.put(first, second);
}
}
Klassentest {
public static void main(String[] args) {
Pair<String,Long> Paar = neues Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Nummer number = pair.addToMap(map);
}
}
Der addToMap() Die Methode fügt den Inhalt des Paares einer Karte hinzu. Jede Karte, die Supertypen von enthalten kann X Und Y würde tun. Die Karte setzen() Die Methode gibt den in der Karte gefundenen Wert für den angegebenen Schlüssel zurück, wenn in der Karte bereits ein Schlüsselwerteintrag für den Schlüssel vorhanden ist. Der Rückgabewert der Karte setzen() Die Methode soll von zurückgegeben werden addToMap() Verfahren. Unter diesen Umständen möchte man die Methode wie oben gezeigt deklarieren: Die Karte wird mit Supertypen der Typparameter des Paars und der parametrisiert addToMap() Der Rückgabetyp der Methode ist der Werttyp der Karte.
Da der Compiler keine unteren Grenzen für Typparameter zulässt, wetten wir eine Problemumgehung.
Eine Lösung, die mir in den Sinn kommt, ist die Verwendung eines Platzhalters, da Platzhalter eine Untergrenze haben können. Hier ist eine Problemumgehung mit einem Platzhalter.
Beispiel (eines Workarounds für das prigeräge Beispiel mit Platzhaltern):
Klassenpaar<X,Y> {
privates X zuerst;
privat Y Sekunde;
...
public Objekt addToMap(Map< ? Super X , ? Super Y >-Karte) {
return map.put(first, second);
}
}
Klassentest {
public static void main(String[] args) {
Pair<String,Long> Paar = neues Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Zahl Zahl = (Nummer) pair.addToMap(map);
}
}
Es funktioniert, außer dass es keine Möglichkeit gibt, den Rückgabetyp wie gewünscht zu deklarieren. Es wäre der Supertyp von Y den der Compiler aus dem Kartentyp erfasst, es gibt jedoch keine Syntax für die Angabe. Wir dürfen den Rückgabetyp nicht als „deklarieren“. ? Super Y “, Weil “ ? Super Y „ ist ein Platzhalter und kein Typ und daher als Rückgabetyp nicht zulässig. Wir haben keine Wahl und müssen verwenden Objekt stattdessen als Rückgabetyp unserer Methode. Dieser eher unspezifische Rückgabetyp zwingt wiederum Aufrufer des addToMap() Methode, um den Rückgabewert von herunterzuwandeln Objekt zu seinem eigentlichen Typ. Das ist nicht genau das, was wir uns vorgestellt hatten.
Eine weitere Problemumgehung ist die Verwendung statischer Methoden. Hier ist eine Problemumgehung mit einer statischen statt einer nicht statischen Methode.
Beispiel (eines Workarounds für das vorherige Beispiel mit einer statischen Methode):
Klassenpaar<X,Y> {
privates X zuerst;
privat Y Sekunde;
...
public statisch < A , B ,X erweitert A ,Y erweitert B > B addToMap(Pair<X,Y> Paar, Map< A , B >-Karte) {
return map.put(pair.first,pair.second);
}
}
Klassentest {
public static void main(String[] args) {
Pair<String,Long> Paar = neues Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Zahl Zahl = Paar .addToMap( Paar ,Karte);
}
}
Das Generikum addToMap() Die Methode verfügt über vier Typparameter: zwei Platzhalter X Und Y für den Typ des Paares und zwei Platzhalter A Und B für den Kartentyp. A Und B sind Supertypen von X Und Y , Weil X Und Y werden mit deklariert A Und B als ihre Obergrenzen. (Beachten Sie, dass die Typparameter der generischen Methode X Und Y habe nichts damit zu tun Paar Klasse X Und Y Parameter. Die Namen X Und Y werden für die generische Methode wiederverwendet, um sie leicht als Typparameter des Paares erkennbar zu machen. Mithilfe von vier Typparametern können wir den genauen Rückgabetyp wie gewünscht deklarieren: Es ist derselbe Typ wie der Werttyp der Karte.
Beispiel (für die illegale Verwendung eines Typparameters im statischen Kontext einer generischen Klasse):
Klasse SomeClass <T> {
// statischer Initialisierer, statisches Feld, statische Methode
statisch {
SomeClass< T >-Test = neues SomeClass< T >(); // Fehler
}
Privat statisch T globalInfo; // Fehler
public statisch T getGlobalInfo() { // Fehler
return globalInfo;
}
// nicht statischer Initialisierer, nicht statisches Feld, nicht statische Methode
{
SomeClass< T >-Test = neues SomeClass< T >();
}
Privat T localInfo;
public T getLocalInfo() {
return localInfo;
}
// statische verschachtelte Typen
public statisch Klasse Fehler verlängert Ausnahme {
Privates Finale T Info; // Fehler
öffentliches Versagen( T t) { info = t; } // Fehler
public T getInfo() { return info; } // Fehler
}
Privat Schnittstelle Kopierbar {
T Kopie(); // Fehler
}
Privat Aufzählung Zustand {
GÜLTIG, UNGÜLTIG;
Privat T Info; // Fehler
public void setInfo( T t) { info = t; } // Fehler
public T getInfo() { return info; } // Fehler
}
// nicht statische verschachtelte Typen
public Klasse Zugriffsberechtigter {
public T getInfo() { return localInfo; }
}
}
Das Beispiel zeigt, dass der Typparameter nicht im statischen Kontext einer generischen Klasse verwendet werden kann. Es zeigt auch, dass verschachtelte Schnittstellen und Aufzählungstypen als statische Typmitglieder der Klasse gelten. Nur innere Klassen, also nicht statische verschachtelte Klassen, können den Typparameter der umschließenden generischen Klasse verwenden.
Der Geltungsbereich des Typparameters einer Schnittstelle umfasst die gesamte Definition der Schnittstelle, mit Ausnahme aller Felder oder verschachtelter Typen. Dies liegt daran, dass in einer Schnittstelle definierte Felder und verschachtelte Typen implizit statisch sind.
Beispiel (für die illegale Verwendung eines Typparameters in einer generischen Schnittstelle):
Schnittstelle SomeInterface <T> {
// Feld
SomeClass< T >-Wert = neuer SomeClass< T >(); // Fehler
// verschachtelter Typ
Klasse Zugriffsberechtigter {
public T getInfo() { // Fehler
Rückgabewert.getGlobalInfo();
}
}
// Methoden
T getValue();
}
Das Beispiel zeigt, dass Felder einer Schnittstelle implizit statisch sind, sodass der Typparameter an keiner Stelle in der Deklaration eines Feldes einer generischen Schnittstelle verwendet werden kann. Ebenso wird die verschachtelte Klasse als statische verschachtelte Klasse und nicht als innere Klasse betrachtet. Aus diesem Grund ist die Verwendung des Typparameters an einer beliebigen Stelle in der verschachtelten Klasse illegal.
Problemumgehung für verschachtelte statische Klassen und Schnittstellen:
Bei statisch verschachtelten Klassen und Schnittstellen ist dies kein großes Problem. Als Workaround können wir die statische Klasse oder Schnittstelle selbst generieren.
Beispiel (Problemumgehung – Generieren Sie den verschachtelten statischen Typ):
Klasse Wrapper <T> {
Privates Finale T dasObjekt;
öffentlicher Wrapper( T t) { theObject = t; }
public T getWrapper() { return theObject; } öffentlich Unveränderlich <T> makeImmutable() {
neues Unveränderliches zurückgeben <T> (dasObjekt);
}
öffentlich Veränderbar <T> makeMutable() {
neues Veränderliches zurückgeben <T> (dasObjekt);
}
private static <A> A makeClone(A theObject) { ... }öffentliche statische Endklasse unveränderlich <A> { // ist jetzt eine generische Klasse
Privates Finale A dasObjekt;öffentlich unveränderlich( A arg) {
theObject = makeClone(arg);
}
public A getWrappedItem() {
return makeClone(theObject);
}
}
öffentliche statische Klasse Veränderbar <A> {
... ähnlich ...
}
}
Für verschachtelte Aufzählungstypen gibt es keine solche Problemumgehung, da sie nicht generisch sein können.
Problemumgehung für verschachtelte statische Klassen:
Wenn der verschachtelte statische Typ eine Klasse ist, besteht eine alternative Problemumgehung darin, die statische Klasse in eine nicht statische innere Klasse umzuwandeln.
Beispiel (Problemumgehung – verwenden Sie eine innere Klasse):
Klasse Wrapper <T> {
Privates Finale T dasObjekt;
öffentlicher Wrapper( T t) { theObject = t; }
public T getWrapper() { return theObject; } public Mutable makeMutable() {
zurückkehren Das. new Mutable(theObject);
}
public Immutable makeImmutable() {
zurückkehren Das. new Immutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }öffentliche Abschlussklasse Immutable { // ist keine statische Klasse mehr
Privates Finale T dasObjekt;öffentlich unveränderlich( T arg) {
theObject = makeClone(arg);
}
public T getWrappedItem() {
return makeClone(theObject);
}
}
öffentliche Klasse Veränderbar
... ähnlich ...
}
}
Diese Problemumgehung bringt einen gewissen Mehraufwand mit sich, da alle inneren Klassen einen versteckten Verweis auf eine Instanz des äußeren Typs haben, den sie in diesem Beispiel weder benötigen noch nutzen; Der versteckte Hinweis ist nur Gepäck. Häufig werden innere Klassen mit Schnittstellen kombiniert, um die innere Klasse als privates Implementierungsdetail der umschließenden Klasse zu halten. Das können wir hier auch tun.
Beispiel (die vorherige Problemumgehung wurde verfeinert):
Klasse Wrapper <T> {
Privates Finale T dasObjekt;
öffentlicher Wrapper( T t) { theObject = t; }
public T getWrapper() { return theObject; } öffentlich Veränderbar <T> makeMutable() {
return this.new Mutable(theObject);
}
öffentlich Unveränderlich <T> makeImmutable() {
return this.new Immutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }öffentliche Schnittstelle Unveränderlich<S> {
S getWrappedItem();
}
öffentliche Schnittstelle Mutable<S> {
S getWrappedItem();
void setWrappedItem(S arg);
}
Privat letzte Klasse ImmutableImplementation implementiert Immutable<T> {private final T theObject;
public ImmutableImplementation(T arg) {
theObject = makeClone(arg);
}
public T getWrappedItem() {
return makeClone(theObject);
}
}
Privat Klasse MutableImplementation implementiert Mutable<T> {
... ähnlich ...
}
}
Wie Sie sehen, müssen die verschachtelten öffentlichen Schnittstellen generisch sein, während die inneren privaten Klassen den Typparameter der umschließenden Klasse verwenden können.

Hinterlassen Sie eine Antwort