Что такое PECS (производитель расширяет потребительский супер)?

я наткнулся на Печ (сокращение от производитель extends и потребителем super) при чтении дженериков.

может кто-нибудь объяснить мне, как использовать Печ для устранения путаницы между extends и super?

11 ответов


tl; dr: "Печ" с точки зрения коллекции. Если вы только вытягивая предметы из общей коллекции, это производитель, и вы должны использовать extends; Если только набивка предметов, это потребитель, и вы должны использовать super. Если вы оба с одной и той же коллекции, вы не должны использовать extends или super.


Предположим, у вас есть метод, который принимает в качестве параметра коллекцию вещей, но вы хотите, чтобы он был более гибким, чем просто принятие Collection<Thing>.

Случай 1: вы хотите пройти через коллекцию и делать вещи с каждым элементом.
Тогда список производитель, поэтому вы должны использовать Collection<? extends Thing>.

рассуждения заключается в том, что Collection<? extends Thing> может содержать любой подтип Thing, и, таким образом, каждый элемент будет вести себя как Thing при выполнении операции. (На самом деле вы ничего не можете добавить к Collection<? extends Thing>, потому что вы не может знать во время выполнения, который конкретные подтип Thing коллекция содержит.)

случай 2: вы хотите добавить вещи в коллекцию.
Тогда список потребитель, поэтому вы должны использовать Collection<? super Thing>.

рассуждение здесь в том, что в отличие от Collection<? extends Thing>, Collection<? super Thing> всегда может держать Thing независимо от того, какой фактический параметризованный тип. Здесь вам все равно, что уже есть в списке, пока это позволит Thing будет добавлено; это то, что ? super Thing гарантии.


принципы, стоящие за этим в информатике, названы в честь

  • ковариация - ? расширяет класса MyClass
  • контравариантность - ? супер MyClass и
  • инвариантность / отсутствие дисперсии-MyClass

изображение ниже должно объяснить концепцию.

Фото предоставлено : Андрей Тюкин

Covariance vs Contravariance


Печ (сокращение от"производитель extends и потребителем super") можно объяснить : получить и поставить принцип

принцип Get и Put (из Java-дженериков и коллекций)

в нем говорится,

  1. использовать расширяет подстановочный знак когда вы только get значения из структуры
  2. использовать супер подстановочный знак когда вы только поставить значения в a структура
  3. и не используйте подстановочный знак когда вы и получить и положить.

давайте разберемся на примере:

1. Для расширенного подстановочного знака (получить значения i.E Producer extends)

вот метод, который принимает коллекцию чисел, преобразует каждый в double, и суммирует их

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

назовем метод :

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;

С, sum() способ использования extends, все следующие вызовы являются законными. Первые два вызова не были бы законными, если бы extends не использовался.

исключение вы ничего не могу поставить в тип, объявленный с extends подстановочный знак-кроме значения null, который принадлежит к каждому типу ссылки:

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. Для подстановочного знака Super (put values i.E потребитель super)

вот метод, который принимает набор чисел и int n, и ставит первый n числа, начиная с нуля, в коллекцию:

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

назовем метод :

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]");

С, count() способ использования super, все следующие вызовы являются законными: Последние два звонка не были бы законными, если бы super не был используемый.

исключение вы не может из типа, объявленного с super подстановочный знак-кроме значения типа Object, который является супертипом любого ссылочного типа:

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. Когда оба Get и Put, не используйте подстановочный знак

всякий раз, когда вы и значения и вам значения из той же структуры, вы не следует использовать подстановочные.

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

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 и потребителем super)

мнемоника → получить и поставить принцип.

этот принцип гласит, что:

  • используйте подстановочный знак extends, когда вы получаете значения только из структуры.
  • используйте супер подстановочный знак, когда вы только помещаете значения в структуру.
  • и не используйте подстановочный знак, когда вы оба получаете и ставите.

в Java параметры и параметры универсального типа не поддержка наследования следующим образом.

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 
}

принцип подстановки Лисков: массивы ковариантные(небезопасно), но дженерики не есть invarient(безопасный). т. е. принцип подстановки не работает с параметризованными типами, что означает, что писать незаконно.
Ковариант просто означает if X является подтипом Y затем X[] также будет подтипом 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  

примеры

ограниченном(т. е. направляясь куда-то) подстановочные: есть 3 разных вкуса подстановочных знаков:

  • в дисперсии/без отклонений: ? или ? extends Object - неограниченная символ. Это означает семью всех типов. Используйте, когда вы оба получите и положите.
  • ковариационный: ? extends T (семья все типы, подтипы T) - подстановочный знак с верхний предел. T - это верхний - самый класс в иерархии наследования. Используйте extends подстановочный знак, когда вы только Get значения из структуры.
  • Contra-дисперсия:? super T ( семьи всех типов, которые являются супертипами T) - подстановочный знак с нижняя граница. T - это ниже - самый класс в иерархии наследования. Используйте super подстановочный знак, когда вы только поставить значения в a структура.

Примечание: подстановочный знак ? означает ноль или один раз, представляет собой неизвестный тип. Подстановочный знак может использоваться как тип параметра, никогда не используемый в качестве аргумента типа для вызова универсального метода, создания экземпляра универсального класса.(т. е. при использовании подстановочного знака, который не используется в другом месте в программе, как мы используем 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.
        */  
    }
}

дженериков и примеры


как я объясняю в мой ответ: к другому вопросу, PECS-это мнемоническое устройство, созданное Джошем Блохом, чтобы помочь запомнить Producer extends, Consumer super.

это означает, что при передаче параметризованного типа в метод будет производства экземпляров T (они будут извлечены из него в некотором роде), ? extends T следует использовать, так как любой экземпляр подкласса T также T.

когда параметризованный тип, передаваемый методу, будет потреблять экземпляров T (они будут переданы ему что-то сделать), ? super T следует использовать, потому что экземпляр T может быть законно передан любому методу, который принимает некоторый супертип T. А Comparator<Number> на Collection<Integer>, например. ? extends T не будет работать, потому что Comparator<Integer> не может работать на Collection<Number>.

обратите внимание, что в целом вы должны использовать только ? extends T и ? super T для параметров некоторых методов. Методы должны просто использовать T в качестве параметра type для универсального возвращаемого типа.


в двух словах легко запомнить Печ

  1. использовать <? extends T> подстановочные если вам нужно получить объект тип T из коллекции.
  2. использовать <? super T> подстановочный знак, если вам нужно поместить объекты типа T in коллекция.
  3. Если вам нужно удовлетворить обе вещи, ну, не используйте подстановочный знак. Как все очень просто.

(добавление ответа, потому что никогда не хватает примеров с Подстановочными знаками дженериков)

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

запомните это:

потребитель едят ужин(супер); производитель выходит фабрика его родителей


предположим эту иерархию:

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

уточним PE-Producer Extends:

List<? extends Shark> sharks = new ArrayList<>();

почему вы не можете добавить объекты, которые расширяют "акулу" в этом списке? например:

sharks.add(new HammerShark());//will result in compilation error

поскольку у вас есть список, который может быть типа A, B или C во время, вы не можете добавить в него какой-либо объект типа A, B или C, потому что вы можете получить комбинацию, которая не разрешена в java.
на практике, компилятор действительно может видеть в compiletime, что вы добавляете B:

sharks.add(new HammerShark());

...но он не может сказать, будет ли во время выполнения ваш B подтипом или супертипом типа списка. Во время выполнения типом списка может быть любой из типов A, B, C. Поэтому вы не можете добавить HammerSkark (super type) в список DeadHammerShark, например.

* вы скажете: "Хорошо, но почему я не могу добавить в него HammerSkark, так как это самый маленький тип?". Ответ: Это самый маленький вы знаю. Купить hammerskark можно будьте расширены кем-то еще, и вы окажетесь в том же сценарии.

давайте уточним CS-Consumer Super:

в той же иерархии мы можем попробовать это:

List<? super Shark> sharks = new ArrayList<>();

что и почему ты can добавить в этот список?

sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());

вы можете добавить вышеуказанные типы объектов, потому что все,что ниже shark(A,B,C) всегда будет подтипами чего-либо выше shark (X,Y, Z). Легкий для понимания.

вы не может добавить типы выше акулы, потому что во время тип добавленного объекта может быть выше в иерархии, чем объявленный тип списка (X,Y, Z). Это запрещено.

но почему вы не можете прочитать из этого списка? (Я имею в виду, что вы можете получить элемент из него, но вы не можете назначить его ничему, кроме объекта o):

Object o;
o = sharks.get(2);// only assignment that works

Animal s;
s = sharks.get(2);//doen't work

во время выполнения тип списка может быть любым типом выше A: X, Y, Z, ... Компилятор может скомпилировать ваше назначение утверждение (которое кажется правильным), но,во время тип S (животное) может быть ниже в иерархии, чем объявленный тип списка(который может быть существом или выше). Это запрещено.

подведем итоги

мы используем:<? super T> чтобы добавить объекты типов, равных или ниже T в списке. мы не можем читать из он.
мы используем:<? extends T> для чтения объектов типов, равных или ниже T из списка. мы не можем добавить элемент к нему.


подстановочные знаки можно использовать тремя способами:

              - Upper bound Wildcard  ( ? extends Type ).

              - Lower bound Wildcard  ( ? super Type ) .

              - Unbounded Wildcard    ( ? ) .

для целей этого обсуждения полезно думать о переменных как об одной из двух функций:

                      - In Variable

                              An "in" variable serves up data to the code. 
                              Imagine a copy method with two arguments: 
                                      copy(src, dest)
                              The src argument provides the data to be copied, so it is the "in" parameter.
                      - Out Variable

                              An "out" variable holds data for use elsewhere. In the copy example, 
                                      copy(src, dest)
                              the dest argument accepts data, so it is the "out" parameter.

              An "in" variable is defined with an upper bounded wildcard, using the extends keyword.
              An "out" variable is defined with a lower bounded wildcard, using the super keyword.
              In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard.
              In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard.

                      class NaturalNumber 
                      {

                              private int i;

                              public NaturalNumber(int i)
                              { 
                                      this.i = i;
                              }
                      }

                          class EvenNumber extends NaturalNumber 
                          {

                              public EvenNumber(int i) 
                              { 
                                      super(i);
                              }
                          }

              Consider the following code:

                      List<EvenNumber> le = new ArrayList<>();
                      List<? extends NaturalNumber> ln = le;
                      ln.add(new NaturalNumber(35));  // compile-time error


                      You can add null.
                      You can invoke clear.
                      You can get the iterator and invoke remove.
                      You can capture the wildcard and write elements that you've read from the list.