Le premier dit que c'est "un type qui est un ancêtre de E"; le second dit que c'est "un type qui est une sous-classe de E". (Dans les deux cas, E lui-même va bien.)
Ainsi, le constructeur utilise le ? extends E
formulaire pour garantir que lorsqu'il récupère les valeurs de la collection, elles seront toutes E ou une sous-classe (c'est-à-dire qu'elle est compatible). La drainTo
méthode essaie de mettre des valeurs dans la collection, donc la collection doit avoir un type d'élément E
ou une superclasse .
À titre d'exemple, supposons que vous ayez une hiérarchie de classes comme celle-ci:
Parent extends Object
Child extends Parent
et a LinkedBlockingQueue<Parent>
. Vous pouvez construire ce passage dans un List<Child>
qui copiera tous les éléments en toute sécurité, car chacun Child
est un parent. Vous ne pouvez pas passer un List<Object>
car certains éléments peuvent ne pas être compatibles avec Parent
.
De même, vous pouvez vider cette file d'attente dans un List<Object>
car tout Parent
est un Object
... mais vous ne pouvez pas le vider dans un List<Child>
car le List<Child>
s'attend à ce que tous ses éléments soient compatibles avec Child
.
? extends InputStream
ou? super InputStream
alors vous pouvez utiliser unInputStream
comme argument.Les raisons en sont basées sur la manière dont Java implémente les génériques.
Un exemple de tableaux
Avec les tableaux, vous pouvez le faire (les tableaux sont covariants)
Mais que se passerait-il si vous essayez de faire cela?
Cette dernière ligne compilerait très bien, mais si vous exécutez ce code, vous pourriez obtenir un fichier
ArrayStoreException
. Parce que vous essayez de mettre un double dans un tableau d'entiers (indépendamment de l'accès via une référence numérique).Cela signifie que vous pouvez tromper le compilateur, mais vous ne pouvez pas tromper le système de type d'exécution. Et c'est ainsi parce que les tableaux sont ce que nous appelons des types réifiables . Cela signifie qu'au moment de l'exécution, Java sait que ce tableau a en fait été instancié sous la forme d'un tableau d'entiers auquel il se trouve simplement accessible via une référence de type
Number[]
.Donc, comme vous pouvez le voir, une chose est le type réel de l'objet, et une autre chose est le type de référence que vous utilisez pour y accéder, non?
Le problème des génériques Java
Maintenant, le problème avec les types génériques Java est que les informations de type sont ignorées par le compilateur et ne sont pas disponibles au moment de l'exécution. Ce processus est appelé effacement de type . Il y a de bonnes raisons d'implémenter des génériques comme celui-ci en Java, mais c'est une longue histoire, et cela doit être lié, entre autres, à la compatibilité binaire avec du code préexistant (voir Comment nous avons obtenu les génériques que nous avons ).
Mais le point important ici est que puisque, au moment de l'exécution, il n'y a pas d'informations de type, il n'y a aucun moyen de garantir que nous ne commettons pas de pollution en tas.
Par exemple,
Si le compilateur Java ne vous empêche pas de faire cela, le système de type d'exécution ne peut pas non plus vous arrêter, car il n'y a aucun moyen, au moment de l'exécution, de déterminer que cette liste était censée être une liste d'entiers uniquement. Le runtime Java vous permettrait de mettre ce que vous voulez dans cette liste, alors qu'il ne devrait contenir que des entiers, car lors de sa création, il a été déclaré sous forme de liste d'entiers.
En tant que tel, les concepteurs de Java ont veillé à ce que vous ne puissiez pas tromper le compilateur. Si vous ne pouvez pas tromper le compilateur (comme nous pouvons le faire avec les tableaux), vous ne pouvez pas non plus tromper le système de type d'exécution.
En tant que tel, nous disons que les types génériques ne sont pas réifiables .
Évidemment, cela entraverait le polymorphisme. Prenons l'exemple suivant:
Maintenant, vous pouvez l'utiliser comme ceci:
Mais si vous essayez d'implémenter le même code avec des collections génériques, vous ne réussirez pas:
Vous obtiendrez des erreurs de compilation si vous essayez de ...
La solution consiste à apprendre à utiliser deux fonctionnalités puissantes des génériques Java appelées covariance et contravariance.
Covariance
Avec la covariance, vous pouvez lire les éléments d'une structure, mais vous ne pouvez rien y écrire. Ce sont toutes des déclarations valables.
Et vous pouvez lire à partir de
myNums
:Parce que vous pouvez être sûr que quel que soit le contenu de la liste réelle, il peut être converti en un nombre (après tout, tout ce qui étend le nombre est un nombre, non?)
Cependant, vous n'êtes pas autorisé à mettre quoi que ce soit dans une structure covariante.
Cela ne serait pas autorisé, car Java ne peut pas garantir quel est le type réel de l'objet dans la structure générique. Il peut s'agir de tout ce qui étend Number, mais le compilateur ne peut pas en être sûr. Vous pouvez donc lire, mais pas écrire.
Contravariance
Avec la contravariance, vous pouvez faire le contraire. Vous pouvez mettre des choses dans une structure générique, mais vous ne pouvez pas en lire.
Dans ce cas, la nature réelle de l'objet est une liste d'objets, et par contravariance, vous pouvez y mettre des nombres, essentiellement parce que tous les nombres ont Object comme ancêtre commun. En tant que tel, tous les nombres sont des objets, et donc cela est valide.
Cependant, vous ne pouvez rien lire en toute sécurité dans cette structure contravariante en supposant que vous obtiendrez un nombre.
Comme vous pouvez le voir, si le compilateur vous permettait d'écrire cette ligne, vous obtiendrez une ClassCastException au moment de l'exécution.
Principe Get / Put
En tant que tel, utilisez la covariance lorsque vous avez uniquement l'intention de retirer des valeurs génériques d'une structure, utilisez la contravariance lorsque vous avez uniquement l'intention de placer des valeurs génériques dans une structure et utilisez le type générique exact lorsque vous avez l'intention de faire les deux.
Le meilleur exemple que j'ai est le suivant qui copie n'importe quel type de nombres d'une liste dans une autre liste. Cela ne fait que éléments de la source et ne place que les éléments dans la cible.
Grâce aux pouvoirs de covariance et de contravariance, cela fonctionne pour un cas comme celui-ci:
la source
List<Object> myObjs = new List<Object();
(qui manque la fermeture>
pour la secondeObject
).super.methodName
. Lors de l'utilisation<? super E>
, cela signifie «quelque chose dans lasuper
direction» par opposition à quelque chose dans laextends
direction. Exemple:Object
est dans lasuper
direction deNumber
(puisqu'il s'agit d'une super classe) etInteger
est dans laextends
direction (puisqu'elle s'étendNumber
).<? extends E>
définitE
comme limite supérieure: "Ceci peut être converti enE
".<? super E>
définitE
comme la limite inférieure: "E
peut être converti en ceci."la source
Object
c'est intrinsèquement une classe de niveau inférieur, malgré sa position en tant que superclasse ultime (et dessinée verticalement dans UML ou des arbres d'héritage similaires). Je n'ai jamais été en mesure d'annuler cela malgré des éons d'essais.Je vais essayer de répondre à cela. Mais pour obtenir une très bonne réponse, vous devriez consulter le livre de Joshua Bloch, Effective Java (2e édition). Il décrit le mnémonique PECS, qui signifie "Producer Extends, Consumer Super".
L'idée est que si votre code consomme les valeurs génériques de l'objet, vous devez utiliser extend. mais si vous produisez de nouvelles valeurs pour le type générique, vous devez utiliser super.
Donc par exemple:
Et
Mais vous devriez vraiment consulter ce livre: http://java.sun.com/docs/books/effective/
la source
<? super E>
veux direany object including E that is parent of E
<? extends E>
veux direany object including E that is child of E .
la source
Vous pouvez rechercher les termes contravariance (
<? super E>
) et covariance (<? extends E>
) sur Google . J'ai trouvé que la chose la plus utile pour comprendre les génériques était pour moi de comprendre la signature de méthode deCollection.addAll
:Tout comme vous voudriez pouvoir ajouter un
String
à unList<Object>
:Vous devriez également pouvoir ajouter un
List<String>
(ou toute collection deString
s) via laaddAll
méthode:Cependant, vous devez comprendre que a
List<Object>
et aList<String>
ne sont pas équivalents et que ce dernier n'est pas non plus une sous-classe du premier. Ce qu'il faut, c'est le concept d'un paramètre de type covariant - c'est-à-dire le<? extends T>
bit.Une fois que vous avez cela, il est simple de penser à des scénarios dans lesquels vous souhaitez également une contravariance (vérifiez l'
Comparable
interface).la source
Avant la réponse; Veuillez être clair que
Exemple:
J'espère que cela vous aidera à comprendre le caractère générique plus clairement.
la source
Un caractère générique avec une limite supérieure ressemble à "? Extend Type" et représente la famille de tous les types qui sont des sous-types de Type, le type Type étant inclus. Le type est appelé la limite supérieure.
Un caractère générique avec une limite inférieure ressemble à "? Super Type" et représente la famille de tous les types qui sont des supertypes de Type, le type Type étant inclus. Le type est appelé la limite inférieure.
la source
Vous avez une classe Parent et une classe Child héritées de la classe Parent.La classe Parent est héritée d'une autre classe appelée GrandParent Class.So l'ordre d'héritage est GrandParent> Parent> Child. Maintenant, <? étend Parent> - Cela accepte la classe Parent ou la classe Child <? super Parent> - Cela accepte la classe Parent ou l'une des classes GrandParent
la source