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

Я столкнулся с PECS (сокращенно для Producer extends и Consumer super) во время чтения на дженериках.

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

Ответы

Ответ 1

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


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

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

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

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

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

Ответ 2

Принципы, лежащие в основе этого в информатике, называются

  • Ковариация: ? extends MyClass,
  • Контравариантность: ? super MyClass и
  • Инвариантность/не дисперсия: MyClass

Картинка ниже должна объяснить концепцию. Фото предоставлено: Андрей Тюкин

Covariance vs Contravariance

Ответ 3

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

Получить и положить принцип (из Java Generics и Collections)

В нем указано,

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

Поясним это на примере:

1. For Extends Wildcard (получить значения i.e Производитель 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, за исключением значения 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 Wildcard (введите значения i.e Consumer 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, за исключением значения типа 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, не используйте wildcard

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

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

Ответ 4

PECS (Производитель extends и Потребитель super)

мнемоника → принцип "получи и положи".

Этот принцип гласит:

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

Пример на 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){} //does not support even though String is subtype of Object

}

Принцип подстановки Лискова: если S является подтипом T, то объекты типа T могут быть заменены объектами типа S.

В системе типов языка программирования существует правило ввода

  • ковариант, если он сохраняет порядок типов (≤), который упорядочивает типы от более специфических к более общим;
  • противоречивый, если он отменяет этот порядок;
  • Инвариант или невариант, если ни один из них не применим.

Ковариация и контравариантность

  • Типы данных (источники) только для чтения могут быть ковариантными;
  • Типы данных (приемники) только для записи могут быть контравариантными.
  • Изменяемые типы данных, которые действуют как источники и приемники, должны быть инвариантными.

Чтобы проиллюстрировать это общее явление, рассмотрим тип массива. Для типа Animal мы можем сделать тип Animal []

  • ковариант: кот [] - это животное [];
  • контравариант: животное [] - это кошка [];
  • инвариант: животное [] не является котом [], а кот [] не является животным [].

Примеры 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  

больше примеров

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

  • In-дисперсия /Non-дисперсия: ? или ? extends Object - Неограниченный Подстановочный знак. Это обозначает семью всех типов. Используйте, когда вы оба получите и положите.
  • Совместная дисперсия: ? extends T (семейство всех типов, являющихся подтипами T) - подстановочный знак с верхней границей. T является верхним классом -most в иерархии наследования. Используйте подстановочный знак extends, когда вы только извлекаете значения из структуры.
  • Противопоставление: ? super T (семейство всех типов, являющихся супертипами T) - подстановочный знак с нижней границей. T является нижним -most классом в иерархии наследования. Используйте подстановочный знак super, когда вы только вставляете значения в структуру.

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

обобщенные и примеры

Ответ 5

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

Ответ 6

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

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

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

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

Ответ 7

Короче говоря, три простых правила, чтобы запомнить PECS:

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

Ответ 8

(добавление ответа, потому что не хватает примеров с универсальными символами 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 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);
        }
    }

Ответ 9

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

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

Позвольте уточнить ЧП - Продюсер продлевает:

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

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

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

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

sharks.add(new HammerShark());

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

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

Позвольте уточнить CS - Consumer Super:

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

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

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

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

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

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

Но почему вы не можете прочитать из этого списка? (Я имею в виду, что вы можете извлечь из него элемент, но вы не можете назначить его чему-либо, кроме Object 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 (Animal) может быть ниже в иерархии, чем объявленный тип списка (который может быть Creature или выше), Это не разрешено.

Подводя итог

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

Ответ 10

Помните об этом:

Потребитель едет ужин (супер); Продюсер расширяет его родительский factory

Ответ 11

Ковариация: принимать подтипы
Контравариантность: принимать супертипы

Ковариантные типы доступны только для чтения, а контравариантные - только для записи.

Ответ 12

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

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

Ответ 13

Используя пример из реальной жизни (с некоторыми упрощениями):

  1. Представьте грузовой поезд с грузовыми вагонами в качестве аналогии с перечнем.
  2. Вы можете поместить груз в грузовой вагон, если груз имеет такой же или меньший размер, чем грузовой вагон = <? super FreightCarSize> <? super FreightCarSize>
  3. Вы можете выгрузить груз из грузового вагона, если у вас достаточно места (больше, чем размер груза) в вашем депо = <? extends DepotSize> <? extends DepotSize>