Что такое PECS (продюсер продвигает Consumer Super)? - PullRequest
653 голосов
/ 27 апреля 2010

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

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

Ответы [ 11 ]

755 голосов
/ 27 апреля 2010

tl; dr:"PECS" - это с точки зрения коллекции. Если вы только извлекаете предметы из общей коллекции, это производитель, и вы должны использовать 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 гарантирует.

506 голосов
/ 02 ноября 2013

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

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

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

Изображение предоставлено: Андрей Тюкин

Covariance vs Contravariance

34 голосов
/ 26 июля 2015

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

мнемоника → Принципы получения и сдачи.

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

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

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

}

Принцип замещения Лискова: если 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  

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

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

  • In-дисперсия / Non-дисперсия: ? или ? extends Object - Unbounded Подстановочный знак. Это обозначает семью всех типов. Используй когда получаешь и ставишь.
  • Co-дисперсия: ? extends T (семейство всех типов, которые являются подтипами T) - подстановочный знак с верхней границей . T является верхним -много классом в иерархии наследования. Используйте подстановочный знак extends, когда вы только получаете значения из структуры.
  • Contra-дисперсия: ? super T (семейство всех типов, которые являются супертипами T) - подстановочный знак с нижней границей . T является нижним -много классом в иерархии наследования. Используйте подстановочный знак 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.
        */  
    }
}

generics и примеры

28 голосов
/ 07 октября 2013
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
    }
}
22 голосов
/ 27 апреля 2010

Как я объясняю в моем ответе на другой вопрос, PECS - это мнемоническое устройство, созданное Джошем Блохом, чтобы помочь запомнить P roducer <strong>e</strong>xtends, C потребитель <strong>s</strong>uper.

Это означает, что когда параметризованный тип, передаваемый методу, будет производить экземпляров 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 в качестве параметра типа для универсального возвращаемого типа.

19 голосов
/ 13 ноября 2014

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

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

(добавление ответа, потому что примеров с универсальными символами недостаточно)

       // 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);
        }
    }
5 голосов
/ 23 сентября 2018

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

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 - Производитель расширяет:

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). Это не разрешено.

Но почему вы не можете читать из этого списка? (Я имею в виду, что вы можете извлечь из него элемент, но вы не можете назначить его чему-либо, кроме объекта 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 из списка. Мы не можем добавить элемент к нему.

2 голосов
/ 19 февраля 2018

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

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

1 голос
/ 07 февраля 2019

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

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

...