El primero dice que es "algún tipo que es un antepasado de E"; el segundo dice que es "algún tipo que es una subclase de E". (En ambos casos, E está bien).
Por lo tanto, el constructor usa el ? extends E
formulario para garantizar que cuando obtenga valores de la colección, todos serán E o alguna subclase (es decir, es compatible). El drainTo
método está tratando de poner valores en la colección, por lo que la colección debe tener un tipo de elemento E
o una superclase .
Como ejemplo, suponga que tiene una jerarquía de clases como esta:
Parent extends Object
Child extends Parent
y a LinkedBlockingQueue<Parent>
. Puede construir este paso en un List<Child>
que copiará todos los elementos de forma segura, porque cada uno Child
es un padre. No se pudo pasar List<Object>
porque algunos elementos podrían no ser compatibles Parent
.
Del mismo modo, puede drenar esa cola en un List<Object>
porque cada Parent
es un Object
... pero no podría drenarlo en un List<Child>
porque List<Child>
espera que todos sus elementos sean compatibles Child
.
? extends InputStream
o? super InputStream
puedes usar unInputStream
como argumento.Las razones para esto se basan en cómo Java implementa genéricos.
Un ejemplo de matrices
Con las matrices puede hacer esto (las matrices son covariantes)
Pero, ¿qué pasaría si intentas hacer esto?
Esta última línea se compilaría bien, pero si ejecuta este código, podría obtener un
ArrayStoreException
. Porque está tratando de poner un doble en una matriz entera (independientemente de que se acceda a través de una referencia numérica).Esto significa que puede engañar al compilador, pero no puede engañar al sistema de tipos de tiempo de ejecución. Y esto es así porque las matrices son lo que llamamos tipos reificables . Esto significa que, en tiempo de ejecución, Java sabe que esta matriz en realidad se instancia como una matriz de enteros a los que simplemente se accede a través de una referencia de tipo
Number[]
.Entonces, como puede ver, una cosa es el tipo real del objeto, y otra es el tipo de referencia que usa para acceder a él, ¿verdad?
El problema con los genéricos de Java
Ahora, el problema con los tipos genéricos de Java es que el compilador descarta la información de tipo y no está disponible en tiempo de ejecución. Este proceso se llama borrado de tipo . Hay buenas razones para implementar genéricos como este en Java, pero esa es una larga historia, y tiene que ver, entre otras cosas, con la compatibilidad binaria con el código preexistente (vea Cómo obtuvimos los genéricos que tenemos ).
Pero el punto importante aquí es que, dado que, en el tiempo de ejecución, no hay información de tipo, no hay forma de garantizar que no estamos cometiendo contaminación por montón.
Por ejemplo,
Si el compilador de Java no le impide hacer esto, el sistema de tipos de tiempo de ejecución tampoco puede detenerlo, porque no hay forma, en tiempo de ejecución, de determinar que se suponía que esta lista era solo una lista de enteros. El tiempo de ejecución de Java le permitirá poner lo que desee en esta lista, cuando solo debe contener enteros, porque cuando se creó, se declaró como una lista de enteros.
Como tal, los diseñadores de Java se aseguraron de que no puedas engañar al compilador. Si no puede engañar al compilador (como podemos hacer con las matrices) tampoco puede engañar al sistema de tipo de tiempo de ejecución.
Como tal, decimos que los tipos genéricos no son reificables .
Evidentemente, esto obstaculizaría el polimorfismo. Considere el siguiente ejemplo:
Ahora puedes usarlo así:
Pero si intenta implementar el mismo código con colecciones genéricas, no tendrá éxito:
Obtendrías errores de compilación si intentas ...
La solución es aprender a usar dos potentes características de los genéricos de Java conocidos como covarianza y contravarianza.
Covarianza
Con la covarianza, puede leer elementos de una estructura, pero no puede escribir nada en ella. Todas estas son declaraciones válidas.
Y puedes leer de
myNums
:Debido a que puede estar seguro de que, sea lo que sea lo que contenga la lista real, se puede convertir a un Número (después de todo, todo lo que se extiende Número es un Número, ¿verdad?)
Sin embargo, no está permitido poner nada en una estructura covariante.
Esto no estaría permitido, porque Java no puede garantizar cuál es el tipo real del objeto en la estructura genérica. Puede ser cualquier cosa que extienda Number, pero el compilador no puede estar seguro. Entonces puedes leer, pero no escribir.
Contravarianza
Con contravarianza puedes hacer lo contrario. Puede poner las cosas en una estructura genérica, pero no puede leerlas.
En este caso, la naturaleza real del objeto es una Lista de objetos y, a través de la contravarianza, puede colocar Números en él, básicamente porque todos los números tienen Objeto como su ancestro común. Como tal, todos los números son objetos y, por lo tanto, esto es válido.
Sin embargo, no puede leer con seguridad nada de esta estructura contravariante, suponiendo que obtendrá un número.
Como puede ver, si el compilador le permitiera escribir esta línea, obtendría una ClassCastException en tiempo de ejecución.
Principio Get / Put
Como tal, use la covarianza cuando solo tenga la intención de tomar valores genéricos de una estructura, use la contravarianza cuando solo intente poner valores genéricos en una estructura y use el tipo genérico exacto cuando tenga la intención de hacer ambas cosas.
El mejor ejemplo que tengo es el siguiente que copia cualquier tipo de números de una lista a otra lista. Solo obtiene elementos de la fuente y solo coloca elementos en el destino.
Gracias a los poderes de covarianza y contravarianza, esto funciona para un caso como este:
fuente
List<Object> myObjs = new List<Object();
(que falta el cierre>
para el segundoObject
).super.methodName
. Cuando se usa<? super E>
, significa "algo en lasuper
dirección" en lugar de algo en laextends
dirección. Ejemplo:Object
está en lasuper
dirección deNumber
(ya que es una superclase) yInteger
está en laextends
dirección (ya que se extiendeNumber
).<? extends E>
se defineE
como el límite superior: "Esto se puede enviar aE
".<? super E>
se defineE
como el límite inferior: "E
se puede lanzar a esto".fuente
Object
es inherentemente una clase de nivel inferior, a pesar de su posición como la superclase final (y dibujada verticalmente en UML o árboles de herencia similares). Nunca he podido deshacer esto a pesar de los eones de intentarlo.Voy a tratar de responder esto. Pero para obtener una respuesta realmente buena, debe consultar el libro de Joshua Bloch Effective Java (2nd Edition). Describe el PECS mnemotécnico, que significa "Producer Extends, Consumer Super".
La idea es que si el código consume los valores genéricos del objeto, entonces debería usar extend. pero si está produciendo nuevos valores para el tipo genérico, debe usar super.
Así por ejemplo:
Y
Pero realmente deberías consultar este libro: http://java.sun.com/docs/books/effective/
fuente
<? super E>
medioany object including E that is parent of E
<? extends E>
medioany object including E that is child of E .
fuente
Es posible que desee buscar en Google los términos contravarianza (
<? super E>
) y covarianza (<? extends E>
). Descubrí que lo más útil al comprender los genéricos era que entendiera la firma del método deCollection.addAll
:Tal como le gustaría poder agregar un
String
a unList<Object>
:También debería poder agregar una
List<String>
(o cualquier colección deString
s) a través deladdAll
método:Sin embargo, debe darse cuenta de que a
List<Object>
y aList<String>
no son equivalentes y tampoco es una subclase de la primera. Lo que se necesita es el concepto de un parámetro de tipo covariante , es decir, el<? extends T>
bit.Una vez que tenga esto, es simple pensar en escenarios donde también desea contravarianza (verifique la
Comparable
interfaz).fuente
Antes de la respuesta; Por favor sea claro que
Ejemplo:
Espero que esto te ayude a entender el comodín más claro.
fuente
Un comodín con un límite superior se parece a "? Extiende Tipo" y representa la familia de todos los tipos que son subtipos de Tipo, incluido el Tipo Tipo. El tipo se llama límite superior.
Un comodín con un límite inferior se parece a "? Super Type" y representa la familia de todos los tipos que son supertipos de Type, incluido Type type. El tipo se llama límite inferior.
fuente
Tiene una clase Parent y una clase Child heredada de la clase Parent. La clase Parent se hereda de otra clase llamada GrandParent Class. Por lo tanto, el orden de herencia es GrandParent> Parent> Child. Ahora, <? extiende Parent>: esto acepta la clase Parent o la clase Child <? super Parent>: acepta la clase Parent o la clase GrandParent
fuente