¿Qué es PECS (Producer Extends Consumer Super)?
Me encontré con PECS (abreviatura de Productor extends
y Consumidorsuper
) mientras leía sobre genéricos.
¿Alguien puede explicarme cómo usar PECS para resolver la confusión entre extends
y super
?
tl;dr: "PECS" es desde el punto de vista de la colección. Si solo estás extrayendo elementos de una colección genérica, es un productor y debes usar extends
; si solo estás metiendo artículos, es un consumidor y debes usarlo super
. Si haces ambas cosas con la misma colección, no deberías usar ni extends
ni 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 archivo Collection<Thing>
.
Caso 1: Quieres revisar la colección y hacer cosas con cada artículo.
Entonces la lista es un productor , por lo que deberías usar un archivo 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 a Thing
cuando realice su operación. (En realidad, no puede agregar nada (excepto nulo) a a Collection<? extends Thing>
, porque no puede saber en tiempo de ejecución qué subtipo específico de Thing
la colección contiene).
Caso 2: Quieres agregar cosas a la colección.
Entonces la lista es un consumidor , por lo que deberías usar un Collection<? super Thing>
.
El razonamiento aquí es que, a diferencia de Collection<? extends Thing>
, Collection<? super Thing>
siempre puede contener a Thing
sin importar cuál sea el tipo parametrizado real. Aquí no le importa lo que ya esté en la lista, siempre y cuando permita Thing
agregar un; esto es lo que ? super Thing
garantiza.
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 de Andrei Tyukin.
PECS es un dispositivo mnemotécnico en los genéricos de Java que nos ayuda a recordar qué palabra clave usar al especificar límites en parámetros de tipo genérico.
1 Productor extiende:
- Se aplica a tipos de retorno y variables con límites superiores.
- Representa un productor de valores.
- Le permite devolver un subtipo más específico del tipo declarado (covariante).
- La covarianza es una relación jerárquica, es decir, una relación "es-un": el gato como animal "es-un".
2 Súper Consumidor:
- Se aplica a parámetros de entrada y variables con límites inferiores.
- Representa un consumidor de valores.
- Le permite aceptar un supertipo más general del tipo declarado (contravariante).
- Contravarianza Inversión de la relación de subtipo: Animal que "puede retener" Cat.
3. Tipos invariantes/no variantes
- (sin límites) tienen relaciones fijas y no se benefician de PECS.
- Relación fija, es decir, el tipo debe permanecer exactamente como se especifica, sin ninguna flexibilidad para aceptar subtipos o supertipos: es decir, donde tanto los tipos de entrada como los de salida son fijos.
El Principio de Sustitución de Liskov (LSP) establece que “ los objetos de un programa deben ser reemplazables con instancias de sus subtipos sin alterar la corrección de ese programa ”.
Imagen original
comodín acotado (es decir, que se dirige hacia algún lugar) : hay 3 tipos diferentes de comodines :
- Covarianza:
? extends T
(Reinado deT
descendientes): un comodín con un límite superior .T
es la clase más alta en la jerarquía de herencia. Utilice unextends
comodín cuando solo obtenga valores de una estructura. Los límites superiores promueven la sustitución segura de subtipos.
Por ejemplo, si T extiende Animal, T puede ser el propio Animal o cualquiera de sus subtipos como Gato, Perro, etc., pero no puede ser un tipo "superior" que Animal, como Objeto. - Contravarianza:
? super T
(Reinado delT
antepasado): un comodín con un límite inferior .T
es la clase más baja en la jerarquía de herencia. Utilice unsuper
comodín cuando solo coloque valores en una estructura. Los límites inferiores promueven la sustitución segura de supertipos. - Con variación/Sin variación:
?
o? extends Object
- Comodín ilimitado . Representa la familia de todo tipo. Úselo cuando obtenga y ponga.
Nota: el comodín ?
significa cero o una vez y representa un tipo desconocido . El comodín se puede usar como tipo de parámetro, nunca 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 un comodín, esa referencia no se usa en ningún otro lugar del programa como lo usamos nosotros T
).
import java.util.ArrayList;
import java.util.List;
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 {
public static void main(String[] args) {
//? extends Shape i.e. can use any sub type of Shape, here Shape is Upper Bound in inheritance hierarchy
List<? extends Shape> intList5 = new ArrayList<Shape>();
List<? extends Shape> intList6 = new ArrayList<Cricle>();
List<? extends Shape> intList7 = new ArrayList<Rectangle>();
List<? extends Shape> intList9 = new ArrayList<Object>();//ERROR.
//? super Shape i.e. can use any super type of Shape, here Shape is Lower Bound in inheritance hierarchy
List<? super Shape> inList5 = new ArrayList<Shape>();
List<? super Shape> inList6 = new ArrayList<Object>();
List<? super Shape> inList7 = new ArrayList<Circle>(); //ERROR.
//-----------------------------------------------------------
Circle circle = new Circle();
Shape shape = circle; // OK. Circle IS-A Shape
List<Circle> circles = new ArrayList<>();
List<Shape> shapes = circles; // ERROR. List<Circle> is not subtype of List<Shape> even when Circle IS-A Shape
List<? extends Circle> circles2 = new ArrayList<>();
List<? extends Shape> shapes2 = circles2; // OK. List<? extends Circle> is subtype of List<? extends Shape>
//-----------------------------------------------------------
Shape shape2 = new Shape();
Circle circle2= (Circle) shape2; // OK. with type casting
List<Shape> shapes3 = new ArrayList<>();
List<Circle> circles3 = shapes3; //ERROR. List<Circle> is not subtype of List<Shape> even Circle is subetype of Shape
List<? super Shape> shapes4 = new ArrayList<>();
List<? super Circle> circles4 = shapes4; //OK.
}
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Object());//ERROR
list.add(new Shape()); //ERROR
list.add(new Circle()); // ERROR
list.add(new Square()); // ERROR
list.add(new Rectangle()); // ERROR
Shape shape= list.get(0);//OK 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 Object());//ERROR
list.add(new Shape());//OK
list.add(new Circle());//OK
list.add(new Square());//OK
list.add(new Rectangle());//OK
Shape shape= list.get(0); // ERROR. Type mismatch, so list acts only as consumer
Object object= list.get(0); //OK 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.
*/
}
}
Más ejemplos de codificación
Visualizando el concepto
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
}
}
En pocas palabras, tres reglas fáciles de recordar PECS:
- Utilice el
<? extends T>
comodín si necesita recuperar un objeto de tipoT
de una colección. - Utilice el
<? super T>
comodín si necesita colocar objetos de tipoT
en una colección. - Si necesita satisfacer ambas cosas, no utilice ningún comodín. Tan sencillo como eso.