Una muy buena explicación con un ejemplo @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630 que explica superparte pero da una idea de otra.
lupchiazoem
Respuestas:
844
tl; dr: "PECS" es desde el punto de vista de la colección. Si solo está sacando artículos de una colección genérica, es un productor y debe usarlo extends; si solo está cargando artículos, es un consumidor y debe usarlo super. Si haces ambas cosas con la misma colección, no deberías usar ninguna extendso super.
Supongamos que tiene un método que toma como parámetro una colección de cosas, pero desea que sea más flexible que simplemente aceptar un Collection<Thing> .
Caso 1: desea revisar la colección y hacer cosas con cada elemento.
Entonces la lista es un productor , por lo que debe usar a Collection<? extends Thing>.
El razonamiento es que a Collection<? extends Thing>podría contener cualquier subtipo de Thingy, por lo tanto, cada elemento se comportará como Thingcuando realice su operación. (En realidad, no puede agregar nada a un Collection<? extends Thing>, porque no puede saber en tiempo de ejecución qué subtipo específico de Thingla colección contiene).
Caso 2: desea agregar cosas a la colección.
Entonces la lista es un consumidor , por lo que debe usar unCollection<? super Thing> .
El razonamiento aquí es que Collection<? extends Thing>, a diferencia , Collection<? super Thing>siempre puede contener un Thingno importa cuál sea el tipo parametrizado real. Aquí no le importa lo que ya está en la lista, siempre y cuando permita Thingque se agregue un; Esto es lo que ? super Thinggarantiza.
Siempre trato de pensarlo de esta manera: un productor puede producir algo más específico, por lo tanto , se extiende , un consumidor puede aceptar algo más general, por lo tanto, súper .
Feuermurmel
10
Otra forma de recordar la distinción productor / consumidor es pensar en la firma de un método. Si tiene un método doSomethingWithList(List list), está consumiendo la lista y, por lo tanto, necesitará covarianza / extensión (o una Lista invariante). Por otro lado, si su método es List doSomethingProvidingList, entonces está produciendo la Lista y necesitará contravarianza / super (o una Lista invariante).
Raman
3
@MichaelMyers: ¿Por qué no podemos simplemente usar un tipo parametrizado para ambos casos? ¿Existe alguna ventaja específica de usar comodines aquí, o es solo un medio de mejorar la legibilidad similar a, por ejemplo, usar referencias a constcomo parámetros del método en C ++ para indicar que el método no modifica los argumentos?
Chatterjee
77
@Raman, creo que lo confundiste. En doSthWithList (puede tener List <? Super Thing>), ya que es un consumidor, puede usar super (recuerde, CS). Sin embargo, es List <? extiende Thing> getList () ya que puede devolver algo más específico al producir (PE).
masterxilo
44
@AZ_ Comparto tu sentimiento. Si un método obtiene () de la lista, el método se consideraría un Consumidor <T> y la lista se consideraría un proveedor; pero la regla de PECS es "desde el punto de vista de la lista", por lo que se requiere 'extender'. Debería ser GEPS: get extend; poner super.
Treefish Zhang
561
Los principios detrás de esto en informática se llaman
Covarianza: ? extends MyClass,
Contravarianza: ? super MyClass y
Invarianza / no varianza: MyClass
La siguiente imagen debería explicar el concepto. Imagen cortesía: Andrey Tyukin
Hola a todos. Soy Andrey Tyukin, solo quería confirmar que anoopelias y DaoWen se pusieron en contacto conmigo y obtuvieron mi permiso para usar el boceto, está licenciado bajo (CC) -BY-SA. Thx @ Anoop por darle una segunda vida ^^ @Brian Agnew: (en "pocos votos"): Eso es porque es un boceto para Scala, usa la sintaxis de Scala y asume una variación del sitio de declaración, que es bastante diferente a la extraña llamada de Java -varianza del sitio ... Tal vez debería escribir una respuesta más detallada que muestre claramente cómo se aplica este boceto a Java ...
Andrey Tyukin
3
¡Esta es una de las explicaciones más simples y claras para la covarianza y contravarianza que he encontrado!
cs4r
@Andrey Tyukin Hola, también quiero usar esta imagen. ¿Cómo puedo contactarte?
Use un comodín extendido cuando solo obtenga valores de una estructura.
Use un súper comodín cuando solo ponga valores en una estructura.
Y no use un comodín cuando ambos se ponen y se ponen.
Ejemplo en Java:
classSuper{Object testCoVariance(){returnnull;}//Covariance of return types in the subtype.void testContraVariance(Object parameter){}// Contravariance of method arguments in the subtype.}classSubextendsSuper{@OverrideString testCoVariance(){returnnull;}//compiles successfully i.e. return type is don't care(String is subtype of Object) @Overridevoid testContraVariance(String parameter){}//doesn't support even though String is subtype of Object}
Los tipos de datos de solo lectura (orígenes) pueden ser covariantes ;
los tipos de datos de solo escritura (sumideros) pueden ser contravariantes .
Los tipos de datos mutables que actúan como fuentes y sumideros deben ser invariables .
Para ilustrar este fenómeno general, considere el tipo de matriz. Para el tipo Animal podemos hacer el tipo Animal []
covariante : un gato [] es un animal [];
contravariante : un animal [] es un gato [];
invariante : un animal [] no es un gato [] y un gato [] no es un animal [].
Ejemplos de Java:
Object name=newString("prem");//worksList<Number> numbers =newArrayList<Integer>();//gets compile time errorInteger[] myInts ={1,2,3,4};Number[] myNumber = myInts;
myNumber[0]=3.14;//attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)List<String> list=newArrayList<>();
list.add("prem");List<Object> listObject=list;//Type mismatch: cannot convert from List<String> to List<Object> at Compiletime
comodín delimitado (es decir, hacia un lugar) : hay 3 tipos diferentes de comodines:
En varianza / no varianza: ?o ? extends Object- Sin límites Comodín sin . Es sinónimo de la familia de todo tipo. Úselo cuando ambos consiguen y ponen.
Covarianza: ? extends T(la familia de todos los tipos que son subtipos de T): un comodín con un límite superior . Tes la clase más alta en la jerarquía de herencia. Use un extendscomodín cuando solo obtenga valores de una estructura.
Contra-varianza: ? super T(la familia de todos los tipos que son supertipos de T): un comodín con un límite inferior . Tes la clase más baja en la jerarquía de herencia. Use un supercomodín cuando solo ponga valores en una estructura.
Nota: comodín ?significa cero o una vez , representa un tipo desconocido. El comodín se puede usar como el tipo de un parámetro, nunca se usa como argumento de tipo para una invocación de método genérico, una creación de instancia de clase genérica (es decir, cuando se usa el comodín esa referencia que no se usa en otra parte del programa como la que usamos T)
classShape{void draw(){}}classCircleextendsShape{void draw(){}}classSquareextendsShape{void draw(){}}classRectangleextendsShape{void draw(){}}publicclassTest{/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */publicvoid testCoVariance(List<?extendsShape> list){
list.add(newShape());// Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(newCircle());// Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(newSquare());// Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(newRectangle());// Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supportingShape shape= list.get(0);//compiles so list act as produces only/*You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can get an object and know that it will be an Shape
*/}/*
* Example for a lower bound wildcard (Put values i.e Consumer`super`)
* */publicvoid testContraVariance(List<?superShape> list){
list.add(newShape());//compiles i.e. inheritance is supporting
list.add(newCircle());//compiles i.e. inheritance is supporting
list.add(newSquare());//compiles i.e. inheritance is supporting
list.add(newRectangle());//compiles i.e. inheritance is supportingShape shape= list.get(0);// Error: Type mismatch, so list acts only as consumerObject object= list.get(0);// gets an object, but we don't know what kind of Object it is./*You can add a Shape,Circle,Square,Rectangle to a List<? super Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/}}
Oye, solo quería saber a qué te referías con la última frase: "Si crees que mi analogía es incorrecta, actualízala". ¿Quiere decir si es éticamente incorrecto (que es subjetivo) o si está equivocado en el contexto de la programación (que es objetivo: no, no está mal)? Me gustaría reemplazarlo con un ejemplo más neutral que sea universalmente aceptable independientemente de las normas culturales y las creencias éticas; Si eso está bien contigo.
Neurona
Por fin pude conseguirlo. Buena explicación
Oleg Kuts el
2
@Premraj, In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.no puedo agregar elementos a List <?> O List <? extiende Object>, por lo que no entiendo por qué puede ser Use when you both get and put.
LiuWenbin_NO.
1
@LiuWenbin_NO. - Esa parte de la respuesta es engañosa. ?- el "comodín ilimitado" - se corresponde con el opuesto exacto de invariancia. Consulte la siguiente documentación: docs.oracle.com/javase/tutorial/java/generics/… que establece: En el caso en que el código necesite acceder a la variable como variable " entrante " y "saliente", haga No use un comodín. (Están usando "in" y "out" como sinónimo de "get" y "put"). Con la excepción de nullque no puede agregar a una Colección parametrizada con ?.
mouselabs
29
publicclassTest{publicclass A {}publicclass B extends A {}publicclass C extends B {}publicvoid testCoVariance(List<?extends B> myBlist){
B b =new B();
C c =new C();
myBlist.add(b);// does not compile
myBlist.add(c);// does not compile
A a = myBlist.get(0);}publicvoid testContraVariance(List<?super B> myBlist){
B b =new B();
C c =new C();
myBlist.add(b);
myBlist.add(c);
A a = myBlist.get(0);// does not compile}}
Entonces "? Extiende B" debe interpretarse como "? Extiende B". Es algo que B extiende para incluir todas las superclases de B hasta Object, excluyendo B en sí. ¡Gracias por el código!
Saurabh Patil
3
@SaurabhPatil No, ? extends Bsignifica B y cualquier cosa que se extienda B.
pregunta el
24
Como explico en mi respuesta a otra pregunta, PECS es un dispositivo mnemónico creado por Josh Bloch para ayudar a recordar a P roducer extends, C consumidor super.
Esto significa que cuando un tipo parametrizado que se pasa a un método producirá instancias de T(se recuperarán de él de alguna manera), ? extends Tdebe usarse, ya que cualquier instancia de una subclase de Ttambién es a T.
Cuando un tipo parametrizado que se pasa a un método consumirá instancias de T(se pasará a él para hacer algo), ? super Tdebe usarse porque una instancia de Tpuede pasar legalmente a cualquier método que acepte algún supertipo de T. A Comparator<Number>podría usarse en un Collection<Integer>, por ejemplo. ? extends Tno funcionaría, porque a Comparator<Integer>no podría funcionar en a Collection<Number>.
Tenga en cuenta que, en general, solo debe usar ? extends Ty ? super Tpara los parámetros de algún método. Los métodos solo deben usarse Tcomo parámetro de tipo en un tipo de retorno genérico.
¿Este principio solo se aplica a las colecciones? Tiene sentido cuando uno trata de correlacionarlo con una lista. Si piensa en la firma de clasificación (Lista <T>, Comparador <? Super T>) ---> aquí, el Comparador usa super, por lo que significa que es un consumidor en el contexto PECS. Cuando observa la implementación, por ejemplo, como: public int compare (Persona a, Persona b) {return a.age <b.age? -1: a.age == b.age? 0: 1; } Siento que la persona no consume nada sino que produce edad. Eso me confunde. ¿Hay algún defecto en mi razonamiento o PECS solo se aplica a las colecciones?
Fatih Arslan
24
En pocas palabras, tres reglas fáciles de recordar PECS:
Use el <? extends T>comodín si necesita recuperar un objeto de tipo Tde una colección.
Use el <? super T>comodín si necesita poner objetos de tipoT en una colección.
Si necesita satisfacer ambas cosas, bueno, no use ningún comodín. Tan simple como eso.
classCreature{}// XclassAnimalextendsCreature{}// YclassFishextendsAnimal{}// ZclassSharkextendsFish{}// AclassHammerSkarkextendsShark{}// BclassDeadHammerSharkextendsHammerSkark{}// C
Aclaremos PE - El productor extiende:
List<?extendsShark> sharks =newArrayList<>();
¿Por qué no puede agregar objetos que extiendan "Tiburón" en esta lista? me gusta:
sharks.add(newHammerShark());//will result in compilation error
Dado que tiene una lista que puede ser de tipo A, B o C en tiempo de ejecución , no puede agregar ningún objeto de tipo A, B o C porque puede terminar con una combinación que no está permitida en Java. En la práctica, el compilador puede ver en el momento de la compilación que agrega un B:
sharks.add(newHammerShark());
... pero no tiene forma de saber si en tiempo de ejecución, su B será un subtipo o supertipo del tipo de lista. En el tiempo de ejecución, el tipo de lista puede ser cualquiera de los tipos A, B, C. Por lo tanto, no puede terminar agregando HammerSkark (super tipo) en una lista de DeadHammerShark, por ejemplo.
* Dirás: "OK, pero ¿por qué no puedo agregar HammerSkark ya que es el tipo más pequeño?". Respuesta: es la más pequeña que conoces. Pero HammerSkark también puede ser extendido por otra persona y terminas en el mismo escenario.
Puede agregar los tipos de objetos anteriores porque cualquier cosa debajo de tiburón (A, B, C) siempre será subtipos de cualquier cosa por encima de tiburón (X, Y, Z). Fácil de comprender.
No puede agregar tipos por encima de Shark, porque en tiempo de ejecución el tipo de objeto agregado puede tener una jerarquía más alta que el tipo declarado de la lista (X, Y, Z). Esto no esta permitido.
¿Pero por qué no puedes leer de esta lista? (Quiero decir que puede obtener un elemento de él, pero no puede asignarlo a otra cosa que no sea el Objeto o):
Object o;
o = sharks.get(2);// only assignment that worksAnimal s;
s = sharks.get(2);//doen't work
En tiempo de ejecución, el tipo de lista puede ser de cualquier tipo por encima de A: X, Y, Z, ... El compilador puede compilar su declaración de asignación (que parece correcta) pero, en tiempo de ejecución, el tipo de s (Animal) puede ser menor en jerarquía que el tipo declarado de la lista (que podría ser Criatura o superior). Esto no esta permitido.
Para resumir
Usamos <? super T>para agregar objetos de tipos iguales o inferiores TalList . No podemos leerlo. Usamos <? extends T>para leer objetos de tipos iguales o inferiores Tde la lista. No podemos agregarle elementos.
(agregando una respuesta porque nunca hay suficientes ejemplos con comodines genéricos)
// Source List<Integer> intList =Arrays.asList(1,2,3);List<Double> doubleList =Arrays.asList(2.78,3.14);List<Number> numList =Arrays.asList(1,2,2.78,3.14,5);// DestinationList<Integer> intList2 =newArrayList<>();List<Double> doublesList2 =newArrayList<>();List<Number> numList2 =newArrayList<>();// Works
copyElements1(intList,intList2);// from int to int
copyElements1(doubleList,doublesList2);// from double to doublestatic<T>void copyElements1(Collection<T> src,Collection<T> dest){for(T n : src){
dest.add(n);}}// Let's try to copy intList to its supertype
copyElements1(intList,numList2);// error, method signature just says "T"// and here the compiler is given // two types: Integer and Number, // so which one shall it be?// PECS to the rescue!
copyElements2(intList,numList2);// possible// copy Integer (? extends T) to its supertype (Number is super of Integer)privatestatic<T>void copyElements2(Collection<?extends T> src,Collection<?super T> dest){for(T n : src){
dest.add(n);}}
Esta es la forma más clara y sencilla para mí pensar en extendidos vs. super:
extendses para leer
superes para escribir
Considero que "PECS" es una forma no obvia de pensar sobre quién es el "productor" y quién es el "consumidor". "PECS" se define desde el punto de vista de la propia recopilación de datos - la colección "consume" si los objetos están siendo escritas a ella (que está consumiendo objetos de código de llamada), y que "produce" si los objetos están siendo leídos de él (ella está produciendo objetos para algún código de llamada). Sin embargo, esto es contrario a cómo se nombra todo lo demás. Las API Java estándar se nombran desde la perspectiva del código de llamada, no desde la colección en sí. Por ejemplo, una vista centrada en la colección de java.util.List debería tener un método llamado "recibir ()" en lugar de "agregar ()" - después de todo,el elemento, pero la lista mismarecibe el elemento
Creo que es más intuitivo, natural y coherente pensar en las cosas desde la perspectiva del código que interactúa con la colección: ¿el código "lee" o "escribe en" la colección? Después de eso, cualquier código escrito en la colección sería el "productor", y cualquier lectura de código de la colección sería el "consumidor".
Me he encontrado con esa misma colisión mental y tendería a estar de acuerdo, excepto que PECS no especifica el nombre del código y los límites de tipo en sí mismos se establecen en las declaraciones de la Colección. Además, en lo que respecta a la denominación, a menudo tiene nombres para producir / consumir colecciones como srcy dst. Entonces, se trata tanto de código como de contenedores al mismo tiempo y terminé pensando en eso: el "código consumidor" se consume de un contenedor productor y el "código productor" se produce para un contenedor consumidor.
mouselabs
4
La "regla" de PECS solo garantiza que lo siguiente sea legal:
Consumidor: sea lo que ?sea, legalmente puede referirse aT
Productor: sea lo que ?sea, legalmente puede ser referido porT
El emparejamiento típico en la línea de List<? extends T> producer, List<? super T> consumeres simplemente garantizar que el compilador pueda hacer cumplir las reglas de relación de herencia estándar "IS-A". Si pudiéramos hacerlo legalmente, podría ser más simple decirlo <T extends ?>, <? extends T>(o mejor aún en Scala, como puede ver arriba, es [-T], [+T]. Desafortunadamente, lo mejor que podemos hacer es <? super T>, <? extends T>.
Cuando encontré esto por primera vez y lo descompuse en mi cabeza, la mecánica tenía sentido, pero el código en sí seguía pareciéndome confuso: seguía pensando "parece que los límites no deberían invertirse así", aunque yo fue claro en lo anterior, que se trata simplemente de garantizar el cumplimiento de las normas estándar de referencia.
Lo que me ayudó fue mirarlo usando la asignación ordinaria como analogía.
Considere el siguiente código de juguete (no listo para producción):
// copies the elements of 'producer' into 'consumer'static<T>void copy(List<?extends T> producer,List<?super T> consumer){for(T t : producer)
consumer.add(t);}
Ilustrando esto en términos de la analogía de la asignación, para consumerel ?comodín (tipo desconocido) es la referencia, el "lado izquierdo" de la asignación, y <? super T>asegura que lo que sea ?, T"IS-A" ?, Tse le puede asignar, porque ?es un supertipo (o como mucho el mismo tipo) que T.
Porque producerla preocupación es la misma, solo se invierte: producerel ?comodín (tipo desconocido) es el referente , el "lado derecho" de la asignación, y <? extends T>garantiza que, sea lo que sea ?, ?"IS-A" T, se pueda asignar a unT , porque ?es un subtipo (o al menos el mismo tipo) que T.
publicclass A {}//B is Apublicclass B extends A {}//C is Apublicclass C extends A {}
Generics le permite trabajar con Tipos dinámicamente de manera segura
//ListAList<A> listA =newArrayList<A>();//add
listA.add(new A());
listA.add(new B());
listA.add(new C());//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListBList<B> listB =newArrayList<B>();//add
listB.add(new B());//get
B b0 = listB.get(0);
Problema
Como la Colección de Java es un tipo de referencia como resultado, tenemos los siguientes problemas:
Problema # 1
//not compiled//danger of **adding** non-B objects using listA reference
listA = listB;
* El genérico de Swift no tiene ese problema porque la Colección es Value type[Acerca de], por lo tanto, se crea una nueva colección
Problema # 2
//not compiled//danger of **getting** non-B objects using listB reference
listB = listA;
La solución: comodines genéricos
El comodín es una característica de tipo de referencia y no se puede instanciar directamente
La solución # 1,<? super A> también conocida como límite inferior, también conocida como contravarianza, también conocida como consumidores, garantiza que sea operada por A y todas las superclases, por eso es seguro agregar
<? extends A>aka límite superior aka covarianza aka productores garantiza que es operado por A y todas las subclases, es por eso que es seguro obtener y lanzar
List<?extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;//get
A a0 = listExtendsA.get(0);
super
parte pero da una idea de otra.Respuestas:
tl; dr: "PECS" es desde el punto de vista de la colección. Si solo está sacando artículos de una colección genérica, es un productor y debe usarlo
extends
; si solo está cargando artículos, es un consumidor y debe usarlosuper
. Si haces ambas cosas con la misma colección, no deberías usar ningunaextends
osuper
.Supongamos que tiene un método que toma como parámetro una colección de cosas, pero desea que sea más flexible que simplemente aceptar un
Collection<Thing>
.Caso 1: desea revisar la colección y hacer cosas con cada elemento.
Entonces la lista es un productor , por lo que debe usar a
Collection<? extends Thing>
.El razonamiento es que a
Collection<? extends Thing>
podría contener cualquier subtipo deThing
y, por lo tanto, cada elemento se comportará comoThing
cuando realice su operación. (En realidad, no puede agregar nada a unCollection<? extends Thing>
, porque no puede saber en tiempo de ejecución qué subtipo específico deThing
la colección contiene).Caso 2: desea agregar cosas a la colección.
Entonces la lista es un consumidor , por lo que debe usar un
Collection<? super Thing>
.El razonamiento aquí es que
Collection<? extends Thing>
, a diferencia ,Collection<? super Thing>
siempre puede contener unThing
no importa cuál sea el tipo parametrizado real. Aquí no le importa lo que ya está en la lista, siempre y cuando permitaThing
que se agregue un; Esto es lo que? super Thing
garantiza.fuente
doSomethingWithList(List list)
, está consumiendo la lista y, por lo tanto, necesitará covarianza / extensión (o una Lista invariante). Por otro lado, si su método esList doSomethingProvidingList
, entonces está produciendo la Lista y necesitará contravarianza / super (o una Lista invariante).const
como parámetros del método en C ++ para indicar que el método no modifica los argumentos?Los principios detrás de esto en informática se llaman
? extends MyClass
,? super MyClass
yMyClass
La siguiente imagen debería explicar el concepto. Imagen cortesía: Andrey Tyukin
fuente
PECS (Productor
extends
y Consumidorsuper
)mnemónico → Obtener y poner principio.
Este principio establece que:
Ejemplo en Java:
Principio de sustitución de Liskov: si S es un subtipo de T, entonces los objetos de tipo T pueden reemplazarse por objetos de tipo S.
Dentro del sistema de tipos de un lenguaje de programación, una regla de tipeo
Covarianza y contravarianza
Para ilustrar este fenómeno general, considere el tipo de matriz. Para el tipo Animal podemos hacer el tipo Animal []
Ejemplos de Java:
más ejemplos
comodín delimitado (es decir, hacia un lugar) : hay 3 tipos diferentes de comodines:
?
o? extends Object
- Sin límites Comodín sin . Es sinónimo de la familia de todo tipo. Úselo cuando ambos consiguen y ponen.? extends T
(la familia de todos los tipos que son subtipos deT
): un comodín con un límite superior .T
es la clase más alta en la jerarquía de herencia. Use unextends
comodín cuando solo obtenga valores de una estructura.? super T
(la familia de todos los tipos que son supertipos deT
): un comodín con un límite inferior .T
es la clase más baja en la jerarquía de herencia. Use unsuper
comodín cuando solo ponga valores en una estructura.Nota: comodín
?
significa cero o una vez , representa un tipo desconocido. El comodín se puede usar como el tipo de un parámetro, nunca se usa como argumento de tipo para una invocación de método genérico, una creación de instancia de clase genérica (es decir, cuando se usa el comodín esa referencia que no se usa en otra parte del programa como la que usamosT
)genéricos y ejemplos
fuente
In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.
no puedo agregar elementos a List <?> O List <? extiende Object>, por lo que no entiendo por qué puede serUse when you both get and put
.?
- el "comodín ilimitado" - se corresponde con el opuesto exacto de invariancia. Consulte la siguiente documentación: docs.oracle.com/javase/tutorial/java/generics/… que establece: En el caso en que el código necesite acceder a la variable como variable " entrante " y "saliente", haga No use un comodín. (Están usando "in" y "out" como sinónimo de "get" y "put"). Con la excepción denull
que no puede agregar a una Colección parametrizada con?
.fuente
? extends B
significa B y cualquier cosa que se extienda B.Como explico en mi respuesta a otra pregunta, PECS es un dispositivo mnemónico creado por Josh Bloch para ayudar a recordar a P roducer
extends
, C consumidorsuper
.Tenga en cuenta que, en general, solo debe usar
? extends T
y? super T
para los parámetros de algún método. Los métodos solo deben usarseT
como parámetro de tipo en un tipo de retorno genérico.fuente
En pocas palabras, tres reglas fáciles de recordar PECS:
<? extends T>
comodín si necesita recuperar un objeto de tipoT
de una colección.<? super T>
comodín si necesita poner objetos de tipoT
en una colección.fuente
asumamos esta jerarquía:
Aclaremos PE - El productor extiende:
¿Por qué no puede agregar objetos que extiendan "Tiburón" en esta lista? me gusta:
Dado que tiene una lista que puede ser de tipo A, B o C en tiempo de ejecución , no puede agregar ningún objeto de tipo A, B o C porque puede terminar con una combinación que no está permitida en Java.
En la práctica, el compilador puede ver en el momento de la compilación que agrega un B:
... pero no tiene forma de saber si en tiempo de ejecución, su B será un subtipo o supertipo del tipo de lista. En el tiempo de ejecución, el tipo de lista puede ser cualquiera de los tipos A, B, C. Por lo tanto, no puede terminar agregando HammerSkark (super tipo) en una lista de DeadHammerShark, por ejemplo.
* Dirás: "OK, pero ¿por qué no puedo agregar HammerSkark ya que es el tipo más pequeño?". Respuesta: es la más pequeña que conoces. Pero HammerSkark también puede ser extendido por otra persona y terminas en el mismo escenario.
Aclaremos CS - Consumer Super:
En la misma jerarquía podemos intentar esto:
¿Qué y por qué puedes agregar a esta lista?
Puede agregar los tipos de objetos anteriores porque cualquier cosa debajo de tiburón (A, B, C) siempre será subtipos de cualquier cosa por encima de tiburón (X, Y, Z). Fácil de comprender.
No puede agregar tipos por encima de Shark, porque en tiempo de ejecución el tipo de objeto agregado puede tener una jerarquía más alta que el tipo declarado de la lista (X, Y, Z). Esto no esta permitido.
¿Pero por qué no puedes leer de esta lista? (Quiero decir que puede obtener un elemento de él, pero no puede asignarlo a otra cosa que no sea el Objeto o):
En tiempo de ejecución, el tipo de lista puede ser de cualquier tipo por encima de A: X, Y, Z, ... El compilador puede compilar su declaración de asignación (que parece correcta) pero, en tiempo de ejecución, el tipo de s (Animal) puede ser menor en jerarquía que el tipo declarado de la lista (que podría ser Criatura o superior). Esto no esta permitido.
Para resumir
Usamos
<? super T>
para agregar objetos de tipos iguales o inferioresT
alList
. No podemos leerlo.Usamos
<? extends T>
para leer objetos de tipos iguales o inferioresT
de la lista. No podemos agregarle elementos.fuente
(agregando una respuesta porque nunca hay suficientes ejemplos con comodines genéricos)
fuente
Esta es la forma más clara y sencilla para mí pensar en extendidos vs. super:
extends
es para leersuper
es para escribirConsidero que "PECS" es una forma no obvia de pensar sobre quién es el "productor" y quién es el "consumidor". "PECS" se define desde el punto de vista de la propia recopilación de datos - la colección "consume" si los objetos están siendo escritas a ella (que está consumiendo objetos de código de llamada), y que "produce" si los objetos están siendo leídos de él (ella está produciendo objetos para algún código de llamada). Sin embargo, esto es contrario a cómo se nombra todo lo demás. Las API Java estándar se nombran desde la perspectiva del código de llamada, no desde la colección en sí. Por ejemplo, una vista centrada en la colección de java.util.List debería tener un método llamado "recibir ()" en lugar de "agregar ()" - después de todo,el elemento, pero la lista mismarecibe el elemento
Creo que es más intuitivo, natural y coherente pensar en las cosas desde la perspectiva del código que interactúa con la colección: ¿el código "lee" o "escribe en" la colección? Después de eso, cualquier código escrito en la colección sería el "productor", y cualquier lectura de código de la colección sería el "consumidor".
fuente
src
ydst
. Entonces, se trata tanto de código como de contenedores al mismo tiempo y terminé pensando en eso: el "código consumidor" se consume de un contenedor productor y el "código productor" se produce para un contenedor consumidor.La "regla" de PECS solo garantiza que lo siguiente sea legal:
?
sea, legalmente puede referirse aT
?
sea, legalmente puede ser referido porT
El emparejamiento típico en la línea de
List<? extends T> producer, List<? super T> consumer
es simplemente garantizar que el compilador pueda hacer cumplir las reglas de relación de herencia estándar "IS-A". Si pudiéramos hacerlo legalmente, podría ser más simple decirlo<T extends ?>, <? extends T>
(o mejor aún en Scala, como puede ver arriba, es[-T], [+T]
. Desafortunadamente, lo mejor que podemos hacer es<? super T>, <? extends T>
.Cuando encontré esto por primera vez y lo descompuse en mi cabeza, la mecánica tenía sentido, pero el código en sí seguía pareciéndome confuso: seguía pensando "parece que los límites no deberían invertirse así", aunque yo fue claro en lo anterior, que se trata simplemente de garantizar el cumplimiento de las normas estándar de referencia.
Lo que me ayudó fue mirarlo usando la asignación ordinaria como analogía.
Considere el siguiente código de juguete (no listo para producción):
Ilustrando esto en términos de la analogía de la asignación, para
consumer
el?
comodín (tipo desconocido) es la referencia, el "lado izquierdo" de la asignación, y<? super T>
asegura que lo que sea?
,T
"IS-A"?
,T
se le puede asignar, porque?
es un supertipo (o como mucho el mismo tipo) queT
.Porque
producer
la preocupación es la misma, solo se invierte:producer
el?
comodín (tipo desconocido) es el referente , el "lado derecho" de la asignación, y<? extends T>
garantiza que, sea lo que sea?
,?
"IS-A"T
, se pueda asignar a unT
, porque?
es un subtipo (o al menos el mismo tipo) queT
.fuente
Recuerda esto:
fuente
Usando el ejemplo de la vida real (con algunas simplificaciones):
<? super FreightCarSize>
<? extends DepotSize>
fuente
Covarianza : aceptar subtipos
Contravarianza : aceptar supertipos
Los tipos covariantes son de solo lectura, mientras que los tipos contravariantes son de solo escritura.
fuente
Veamos un ejemplo.
Generics le permite trabajar con Tipos dinámicamente de manera segura
Problema
Como la Colección de Java es un tipo de referencia como resultado, tenemos los siguientes problemas:
Problema # 1
* El genérico de Swift no tiene ese problema porque la Colección es
Value type
[Acerca de], por lo tanto, se crea una nueva colecciónProblema # 2
La solución: comodines genéricos
El comodín es una característica de tipo de referencia y no se puede instanciar directamente
La solución # 1,
<? super A>
también conocida como límite inferior, también conocida como contravarianza, también conocida como consumidores, garantiza que sea operada por A y todas las superclases, por eso es seguro agregarSolución n. ° 2
<? extends A>
aka límite superior aka covarianza aka productores garantiza que es operado por A y todas las subclases, es por eso que es seguro obtener y lanzarfuente