Der erste sagt, dass es "ein Typ ist, der ein Vorfahr von E ist"; der zweite sagt, dass es "ein Typ ist, der eine Unterklasse von E ist". (In beiden Fällen ist E selbst in Ordnung.)
Der Konstruktor verwendet das ? extends E
Formular, um sicherzustellen, dass beim Abrufen von Werten aus der Auflistung alle E oder eine Unterklasse sind (dh kompatibel). Die drainTo
Methode versucht, Werte in die Auflistung einzufügen, daher muss die Auflistung einen Elementtyp E
oder eine Oberklasse haben .
Angenommen, Sie haben eine Klassenhierarchie wie diese:
Parent extends Object
Child extends Parent
und a LinkedBlockingQueue<Parent>
. Sie können diese Übergabe in einer List<Child>
erstellen, die alle Elemente sicher kopiert, da alle Child
übergeordnete Elemente sind . Sie konnten a nicht übergeben, List<Object>
da einige Elemente möglicherweise nicht kompatibel sind Parent
.
Ebenso können Sie diese Warteschlange in eine entleeren, List<Object>
weil jede Parent
eine Object
... aber Sie können sie nicht in eine entleeren, List<Child>
weil List<Child>
erwartet wird, dass alle ihre Elemente mit kompatibel sind Child
.
? extends InputStream
oder? super InputStream
dann kannst du einInputStream
als Argument verwenden.Die Gründe dafür hängen davon ab, wie Java Generika implementiert.
Ein Arrays-Beispiel
Mit Arrays können Sie dies tun (Arrays sind kovariant)
Aber was würde passieren, wenn Sie dies versuchen?
Diese letzte Zeile würde gut kompiliert werden, aber wenn Sie diesen Code ausführen, könnten Sie eine bekommen
ArrayStoreException
. Weil Sie versuchen, ein Double in ein Integer-Array einzufügen (unabhängig davon, ob Sie über eine Zahlenreferenz darauf zugreifen).Dies bedeutet, dass Sie den Compiler täuschen können, aber Sie können das Laufzeitsystem nicht täuschen. Und das ist so, weil Arrays das sind, was wir als reifizierbare Typen bezeichnen . Dies bedeutet, dass Java zur Laufzeit weiß, dass dieses Array tatsächlich als Array von Ganzzahlen instanziiert wurde, auf die einfach über eine Referenz vom Typ zugegriffen wird
Number[]
.Wie Sie sehen, ist eine Sache der tatsächliche Typ des Objekts und eine andere die Art der Referenz, mit der Sie darauf zugreifen, oder?
Das Problem mit Java Generics
Das Problem bei generischen Java-Typen besteht nun darin, dass die Typinformationen vom Compiler verworfen werden und zur Laufzeit nicht verfügbar sind. Dieser Vorgang wird als Typlöschung bezeichnet . Es gibt gute Gründe, solche Generika in Java zu implementieren, aber das ist eine lange Geschichte, und sie hat unter anderem mit der binären Kompatibilität mit bereits vorhandenem Code zu tun (siehe Wie wir die Generika erhalten haben, die wir haben ).
Der wichtige Punkt hierbei ist jedoch, dass es zur Laufzeit keine Typinformationen gibt und es keine Möglichkeit gibt, sicherzustellen, dass wir keine Haufenverschmutzung begehen.
Zum Beispiel,
Wenn der Java-Compiler Sie nicht daran hindert, kann Sie das Laufzeitsystem auch nicht daran hindern, da zur Laufzeit nicht festgestellt werden kann, dass diese Liste nur eine Liste von Ganzzahlen sein sollte. Mit der Java-Laufzeit können Sie alles, was Sie wollen, in diese Liste einfügen, wenn sie nur Ganzzahlen enthalten soll, da sie beim Erstellen als Ganzzahlliste deklariert wurde.
Daher haben die Entwickler von Java sichergestellt, dass Sie den Compiler nicht täuschen können. Wenn Sie den Compiler nicht täuschen können (wie wir es mit Arrays tun können), können Sie auch das Laufzeitsystem nicht täuschen.
Daher sagen wir, dass generische Typen nicht reifizierbar sind .
Offensichtlich würde dies den Polymorphismus behindern. Betrachten Sie das folgende Beispiel:
Jetzt können Sie es so verwenden:
Wenn Sie jedoch versuchen, denselben Code mit generischen Sammlungen zu implementieren, ist dies nicht erfolgreich:
Sie würden Compiler-Fehler bekommen, wenn Sie versuchen ...
Die Lösung besteht darin, zu lernen, zwei leistungsstarke Funktionen von Java-Generika zu verwenden, die als Kovarianz und Kontravarianz bekannt sind.
Kovarianz
Mit Kovarianz können Sie Elemente aus einer Struktur lesen, aber nichts hineinschreiben. All dies sind gültige Erklärungen.
Und Sie können lesen aus
myNums
:Da Sie sicher sein können, dass die aktuelle Liste in eine Nummer hochgesendet werden kann (schließlich ist alles, was die Nummer erweitert, eine Nummer, oder?)
Sie dürfen jedoch nichts in eine kovariante Struktur einfügen.
Dies wäre nicht zulässig, da Java nicht garantieren kann, welcher Objekttyp in der generischen Struktur tatsächlich vorhanden ist. Es kann alles sein, was Number erweitert, aber der Compiler kann nicht sicher sein. Sie können also lesen, aber nicht schreiben.
Kontravarianz
Mit Kontravarianz können Sie das Gegenteil tun. Sie können Dinge in eine generische Struktur einfügen, aber Sie können nicht daraus vorlesen.
In diesem Fall ist die tatsächliche Natur des Objekts eine Liste von Objekten, und durch Kontravarianz können Sie Zahlen einfügen, im Grunde genommen, weil alle Zahlen Objekt als gemeinsamen Vorfahren haben. Als solche sind alle Zahlen Objekte, und daher ist dies gültig.
Sie können jedoch nichts aus dieser kontravarianten Struktur sicher lesen, vorausgesetzt, Sie erhalten eine Zahl.
Wie Sie sehen können, erhalten Sie zur Laufzeit eine ClassCastException, wenn der Compiler Ihnen das Schreiben dieser Zeile erlaubt.
Get / Put-Prinzip
Verwenden Sie daher die Kovarianz, wenn Sie nur generische Werte aus einer Struktur entfernen möchten, verwenden Sie die Kontravarianz, wenn Sie nur generische Werte in eine Struktur einfügen möchten, und verwenden Sie den genauen generischen Typ, wenn Sie beides ausführen möchten.
Das beste Beispiel, das ich habe, ist das folgende, das jede Art von Zahlen von einer Liste in eine andere Liste kopiert. Es nur bekommt Elemente aus der Quelle, und es nur bringt Elemente im Ziel.
Dank der Kraft der Kovarianz und Kontravarianz funktioniert dies für einen Fall wie diesen:
quelle
List<Object> myObjs = new List<Object();
(was den Abschluss>
für die Sekunde fehltObject
).super.methodName
. Bei der Verwendung<? super E>
bedeutet es "etwas in dersuper
Richtung" im Gegensatz zu etwas in derextends
Richtung. Beispiel:Object
ist in dersuper
Richtung vonNumber
(da es eine Superklasse ist) undInteger
ist in derextends
Richtung (da es sich ausdehntNumber
).<? extends E>
definiertE
als Obergrenze: "Dies kann umgewandelt werdenE
".<? super E>
definiertE
als die Untergrenze: "E
kann dazu gegossen werden."quelle
Object
ist es von Natur aus eine niedrigere Klasse, trotz ihrer Position als ultimative Oberklasse (und vertikal in UML oder ähnlichen Vererbungsbäumen gezeichnet). Ich konnte dies trotz Äonen des Versuchs nie rückgängig machen.Ich werde versuchen, dies zu beantworten. Aber um eine wirklich gute Antwort zu erhalten, sollten Sie Joshua Blochs Buch Effective Java (2nd Edition) lesen. Er beschreibt die Mnemonik PECS, die für "Producer Extends, Consumer Super" steht.
Die Idee ist, dass Sie Erweiterungen verwenden sollten, wenn Ihr Code die generischen Werte aus dem Objekt verbraucht. Wenn Sie jedoch neue Werte für den generischen Typ erstellen, sollten Sie super verwenden.
Also zum Beispiel:
Und
Aber wirklich sollten Sie dieses Buch lesen : http://java.sun.com/docs/books/effective/
quelle
<? super E>
meintany object including E that is parent of E
<? extends E>
meintany object including E that is child of E .
quelle
Möglicherweise möchten Sie nach den Begriffen contravariance (
<? super E>
) und covariance (<? extends E>
) googeln . Ich fand, dass das Nützlichste beim Verständnis von Generika für mich war, die Methodensignatur von zu verstehenCollection.addAll
:So wie Sie in der Lage sein möchten, a
String
zu einem hinzuzufügenList<Object>
:Sie sollten auch in der Lage sein, eine
List<String>
(oder eine beliebige Sammlung vonString
s) über die folgendeaddAll
Methode hinzuzufügen :Sie sollten jedoch erkennen, dass a
List<Object>
und aList<String>
nicht gleichwertig sind und dass letztere auch keine Unterklasse der ersteren sind. Was benötigt wird, ist das Konzept eines kovarianten Typparameters - dh der<? extends T>
Bits.Sobald Sie dies haben, ist es einfach, sich Szenarien vorzustellen, in denen Sie auch Kontravarianz wünschen (überprüfen Sie die
Comparable
Schnittstelle).quelle
Vor der Antwort; Bitte stellen Sie das klar
Beispiel:
Hoffe, dies wird Ihnen helfen, Wildcard klarer zu verstehen.
quelle
Ein Platzhalter mit einer Obergrenze sieht aus wie "? Erweitert Typ" und steht für die Familie aller Typen, die Untertypen des Typs sind, wobei der Typ Typ enthalten ist. Typ wird als Obergrenze bezeichnet.
Ein Platzhalter mit einer Untergrenze sieht aus wie "? Super Type" und steht für die Familie aller Typen, bei denen es sich um Supertypen des Typs handelt, wobei der Typ Type enthalten ist. Typ wird als Untergrenze bezeichnet.
quelle
Sie haben eine Parent-Klasse und eine Child-Klasse, die von der Parent-Klasse geerbt wurden. Die Parent-Klasse wird von einer anderen Klasse namens GrandParent-Klasse geerbt. Die Reihenfolge der Vererbung lautet also GrandParent> Parent> Child. Nun, <? erweitert Parent> - Dies akzeptiert Parent-Klasse oder eine Child-Klasse <? super Parent> - Dies akzeptiert die Parent-Klasse oder eine GrandParent-Klasse
quelle