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 usarlo super
. Si haces ambas cosas con la misma colección, no deberías usar ninguna extends
o 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 Thing
y, por lo tanto, cada elemento se comportará como Thing
cuando 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 Thing
la 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 Thing
no importa cuál sea el tipo parametrizado real. Aquí no le importa lo que ya está en la lista, siempre y cuando permita Thing
que se agregue un; Esto es lo que ? super Thing
garantiza.
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).
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
PECS (Productor extends
y Consumidor super
)
mnemónico → Obtener y poner principio.
Este principio establece que:
Ejemplo en Java:
class Super {
Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}
class Sub extends Super {
@Override
String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object)
@Override
void testContraVariance(String parameter){} //doesn't support even though String is subtype of Object
}
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
Para ilustrar este fenómeno general, considere el tipo de matriz. Para el tipo Animal podemos hacer el tipo Animal []
Ejemplos de Java:
Object name= new String("prem"); //works
List<Number> numbers = new ArrayList<Integer>();//gets compile time error
Integer[] 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=new ArrayList<>();
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:
?
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 de T
): un comodín con un límite superior . T
es la clase más alta en la jerarquía de herencia. Use un extends
comodín cuando solo obtenga valores de una estructura.? super T
(la familia de todos los tipos que son supertipos de T
): un comodín con un límite inferior . T
es la clase más baja en la jerarquía de herencia. Use un super
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 usamos T
)
class Shape { void draw() {}}
class Circle extends Shape {void draw() {}}
class Square extends Shape {void draw() {}}
class Rectangle extends Shape {void draw() {}}
public class Test {
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Shape()); // Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(new Circle()); // Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(new Square()); // Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(new Rectangle()); // Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
Shape 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`)
* */
public void testContraVariance(List<? super Shape> list) {
list.add(new Shape());//compiles i.e. inheritance is supporting
list.add(new Circle());//compiles i.e. inheritance is supporting
list.add(new Square());//compiles i.e. inheritance is supporting
list.add(new Rectangle());//compiles i.e. inheritance is supporting
Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
Object 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.
*/
}
}
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
.
?
- 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 null
que no puede agregar a una Colección parametrizada con ?
.
public class Test {
public class A {}
public class B extends A {}
public class C extends B {}
public void 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);
}
public void 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
}
}
? 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 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 T
debe usarse, ya que cualquier instancia de una subclase deT
también es aT
.Cuando un tipo parametrizado que se pasa a un método consumirá instancias de
T
(se pasará a él para hacer algo),? super T
debe usarse porque una instancia deT
puede pasar legalmente a cualquier método que acepte algún supertipo deT
. AComparator<Number>
podría usarse en unCollection<Integer>
, por ejemplo.? extends T
no funcionaría, porque aComparator<Integer>
no podría funcionar en aCollection<Number>
.
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 usarse T
como parámetro de tipo en un tipo de retorno genérico.
En pocas palabras, tres reglas fáciles de recordar PECS:
<? extends T>
comodín si necesita recuperar un objeto de tipo T
de una colección.<? super T>
comodín si necesita poner objetos de tipoT
en una colección.asumamos esta jerarquía:
class Creature{}// X
class Animal extends Creature{}// Y
class Fish extends Animal{}// Z
class Shark extends Fish{}// A
class HammerSkark extends Shark{}// B
class DeadHammerShark extends HammerSkark{}// C
Aclaremos PE - El productor extiende:
List<? extends Shark> sharks = new ArrayList<>();
¿Por qué no puede agregar objetos que extiendan "Tiburón" en esta lista? me gusta:
sharks.add(new HammerShark());//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(new HammerShark());
... 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:
List<? super Shark> sharks = new ArrayList<>();
¿Qué y por qué puedes agregar a esta lista?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
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 works
Animal 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 T
alList
. No podemos leerlo.
Usamos <? extends T>
para leer objetos de tipos iguales o inferiores T
de 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);
// Destination
List<Integer> intList2 = new ArrayList<>();
List<Double> doublesList2 = new ArrayList<>();
List<Number> numList2 = new ArrayList<>();
// Works
copyElements1(intList,intList2); // from int to int
copyElements1(doubleList,doublesList2); // from double to double
static <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)
private static <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:
extends
es para leer
super
es 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".
src
y 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.
La "regla" de PECS solo garantiza que lo siguiente sea legal:
?
sea, legalmente puede referirse a T
?
sea, legalmente puede ser referido por T
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):
// 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 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) que T
.
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) que T
.
Usando el ejemplo de la vida real (con algunas simplificaciones):
<? super FreightCarSize>
<? extends DepotSize>
Covarianza : aceptar subtipos
Contravarianza : aceptar supertipos
Los tipos covariantes son de solo lectura, mientras que los tipos contravariantes son de solo escritura.
Veamos un ejemplo.
public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }
Generics le permite trabajar con Tipos dinámicamente de manera segura
//ListA
List<A> listA = new ArrayList<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);
//ListB
List<B> listB = new ArrayList<B>();
//add
listB.add(new B());
//get
B b0 = listB.get(0);
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;
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
List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();
//add
listSuperA.add(new A());
listSuperA.add(new B());
//get
Object o0 = listSuperA.get(0);
Solució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 lanzar
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
super
parte pero da una idea de otra.