Pregunta ¿Qué es PECS (Producer Extends Consumer Super)?


Me encontré con PECS (abreviatura de Productor extends y el consumidor super) mientras lees sobre genéricos.

¿Puede alguien explicarme cómo usar PECS para resolver la confusión entre extends y super?


548
2018-04-27 17:16


origen


Respuestas:


tl; dr: "PECS" es desde el punto de vista de la colección. Si usted es solamente tirando artículos de una colección genérica, es un productor y debes usar extends; si usted es solamente rellenar artículos, es un consumidor y debes usar super. Si haces ambas cosas con la misma colección, no deberías usar 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 una productor, entonces deberías usar un Collection<? extends Thing>.

El razonamiento es que Collection<? extends Thing> podría contener cualquier subtipo de Thing, y así cada elemento se comportará como Thing cuando realizas tu operación (En realidad no puede agregar nada a un Collection<? extends Thing>, porque no se puede saber en tiempo de ejecución cuál específico subtipo de Thing la colección se mantiene.)

Caso 2: desea agregar cosas a la colección.
Entonces la lista es una consumidor, entonces deberías usar un Collection<? super Thing>.

El razonamiento aquí es que a diferencia Collection<? extends Thing>, Collection<? super Thing> siempre puede sostener una Thing no importa cuál sea el tipo de parámetro real. Aquí no le importa lo que ya está en la lista, siempre que permita un Thing para ser agregado; esto es lo que ? super Thing garantías.


632
2018-04-27 17:37



Los principios detrás de esto en Ciencias de la computación lleva el nombre de

  • Covarianza -? extiende MyClass,
  • Contravarianza -? super MyClass y
  • Invarianza / no varianza - MyClass

La siguiente imagen debe explicar el concepto.

Cortesía de la imagen: Andrey Tyukin

Covariance vs Contravariance


444
2017-11-02 06:34



PECS (abreviatura de "Productor extends y el consumidor super") se puede explicar por: Principio de obtener y poner

Principio Get and Put (de genéricos y colecciones de Java)

Afirma,

  1. usa un extiende el comodín cuando solo obtener valores de una estructura
  2. usar una super comodín cuando solo poner valores en una estructura
  3. y no uses un comodín cuando tú ambos se ponen y ponen.

Comprendamos por ejemplo:

1. Para Extends Wildcard (obtener valores, por ejemplo, Productor extends)

Aquí hay un método, que toma una colección de números, convierte cada uno a un doubley los resume

public static double sum(Collection<? extends Number> nums) {
   double s = 0.0;
   for (Number num : nums) 
      s += num.doubleValue();
   return s;
}

Vamos a llamar al método:

List<Integer>ints = Arrays.asList(1,2,3);
assert sum(ints) == 6.0;
List<Double>doubles = Arrays.asList(2.78,3.14);
assert sum(doubles) == 5.92;
List<Number>nums = Arrays.<Number>asList(1,2,2.78,3.14);
assert sum(nums) == 8.92;

Ya que, sum() usos del método extends, todas las siguientes llamadas son legales. Las dos primeras llamadas no serían legales si no se utilizó extensiones.

EXCEPCIÓN : Usted no puedo poner nada  en un tipo declarado con un extends comodín, excepto por el valor null, que pertenece a cada tipo de referencia:

List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(null);  // ok
assert nums.toString().equals("[1, 2, null]");

2. Para Super Comodín (poner valores, es decir, Consumidor super)

Aquí hay un método, que toma una colección de números y una int n, y pone el primero n enteros, comenzando desde cero, en la colección:

public static void count(Collection<? super Integer> ints, int n) {
    for (int i = 0; i < n; i++) ints.add(i);
}

Vamos a llamar al método:

List<Integer>ints = new ArrayList<Integer>();
count(ints, 5);
assert ints.toString().equals("[0, 1, 2, 3, 4]");
List<Number>nums = new ArrayList<Number>();
count(nums, 5); nums.add(5.0);
assert nums.toString().equals("[0, 1, 2, 3, 4, 5.0]");
List<Object>objs = new ArrayList<Object>();
count(objs, 5); objs.add("five");
assert objs.toString().equals("[0, 1, 2, 3, 4, five]");

Ya que, count() usos del método super, todas las siguientes llamadas son legales: Las últimas dos llamadas no serían legales si no se usó súper.

EXCEPCIÓN : tú no puede obtener nada de un tipo declarado con un super comodín, excepto por un valor de tipo Object, que es un supertipo de cada tipo de referencia:

List<Object> objs = Arrays.<Object>asList(1,"two");
List<? super Integer> ints = objs;
String str = "";
for (Object obj : ints) str += obj.toString();
assert str.equals("1two");

3. Cuando ambos obtienen y ponen, no usan comodines

Cuando usted ambos pusieron valores en y obten valores fuera de la misma estructura, usted no debería usar un comodín.

public static double sumCount(Collection<Number> nums, int n) {
   count(nums, n);
   return sum(nums);
}

126
2018-01-25 13:39



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
    }
}

26
2017-10-07 18:15



PECS (Productor extends y el consumidor super)

mnemónico → Principio de Get and Put.

Este principio establece que:

  • Use un comodín de extensión cuando solo obtiene valores de una estructura.
  • Use un super comodín cuando solo coloca valores en una estructura.
  • Y no uses comodines cuando los consigues y los pones.

En Java, los parámetros y los parámetros de tipo genérico no admiten la herencia de la siguiente manera.

class Super {
    void testCoVariance(Object parameter){} // method Consumes the Object
    Object testContraVariance(){ return null;} //method Produces the Object
}

class Sub extends Super {
    @Override
    void testCoVariance(String parameter){} //doesn't support eventhough String is subtype of Object

    @Override
    String testContraVariance(){ return null;} //compiles successfully i.e. return type is don't care 
}

Principio de sustitución de Liskov:  Arrays son covariantes (inseguras) pero los genéricos no son, por ejemplo, invarientes (seguros). es decir, el Principio de Sustitución no funciona con los tipos parametrizados, lo que significa que es ilegal escribir.
Covariante simplemente significa si X es un subtipo de Y entonces X[] también será subtipo de Y[].

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  

más ejemplos 

encerrado(es decir, dirigirse hacia algún lugar) comodín : Hay 3 sabores diferentes de comodines:

  • Invarianza / No varianza: ? o ? extends Object - Ilimitado Comodín. Representa la familia de todos los tipos. Úselo cuando ambos se ponen y se ponen.
  • Co-varianza: ? extends T (la familia de todos los tipos que son subtipos de T) - un comodín con un límite superior. T es el Superior-la mayoría de las clases en la jerarquía de herencia. Use una extends comodín cuando solo Obtener 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. T es el inferior-la mayoría de las clases en la jerarquía de herencia. Usar una super comodín cuando solo Poner valores en una estructura.

Nota: comodín ? medio cero o una vez, representa un tipo desconocido. El comodín puede usarse como el tipo de un parámetro, nunca utilizado como un 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 utiliza comodín esa referencia no se utiliza en ningún otro lugar del programa como usamos T)

enter image description here

class Shape { void draw() {}}

class Circle extends Shape {void draw() {}}

class Square extends Shape {void draw() {}}

class Rectangle extends Shape {void draw() {}}

public class TestContraVariance {
 /*
   * 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<? extends Shape> 
        * You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
        */  
    }
}

genéricos y ejemplos


24
2017-07-26 06:33



Como explico en mi respuesta a otra pregunta, PECS es un dispositivo mnemónico creado por Josh Bloch para ayudar a recordar PAGroducer mixtends, doonsumer super.

Esto significa que cuando se pasa un tipo parametrizado a un método Produce instancias de T (se recuperarán de alguna manera), ? extends T debería utilizarse, ya que cualquier instancia de una subclase de T también es un T.

Cuando se pasa un tipo parametrizado a un método consumir instancias de T (se pasarán a él para hacer algo), ? super T debe usarse porque una instancia de T legalmente se puede pasar a cualquier método que acepte algún supertipo de T. UN Comparator<Number> podría ser utilizado en una Collection<Integer>, por ejemplo. ? extends T no funcionaría, porque un Comparator<Integer> no pudo operar en una Collection<Number>.

Tenga en cuenta que generalmente solo debe usar ? extends T y ? super T para los parámetros de algún método. Los métodos deberían simplemente usar T como el parámetro de tipo en un tipo de devolución genérico.


18
2018-04-27 17:32



En pocas palabras, fácil de recordar PECS

  1. Utilizar el <? extends T> comodín si necesita recuperar el objeto de tipo T de una colección.
  2. Utilizar el <? super T> comodín si necesita poner objetos de tipo T en Una colección.
  3. Si necesita satisfacer ambas cosas, bueno, no use ningún comodín. Como simple como es.

14
2017-11-13 06:51



(agregando una respuesta porque nunca hay suficientes ejemplos con los comodines de Generics)

       // 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);
        }
    }

2
2018-05-17 18:12



Recuerda esto:

Consumidor come cena(súper); Productor se extiende la fábrica de sus padres


2
2018-02-19 09:50