Что такое PECS (продюсер расширяет потребительскую ценность)?

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

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


person peakit    schedule 27.04.2010    source источник
comment
Очень хорошее объяснение с примером @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630, который объясняет super часть, но дает представление о другой.   -  person lupchiazoem    schedule 20.01.2019


Ответы (15)


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 гарантирует.

person Michael Myers    schedule 27.04.2010
comment
Я всегда пытаюсь думать об этом так: продюсер может создавать что-то более конкретное, следовательно, extends, потребитель разрешен принять что-то более общее, отсюда супер. - person Feuermurmel; 07.05.2013
comment
Еще один способ запомнить различие между производителем и потребителем - это подумать о сигнатуре метода. Если у вас есть метод doSomethingWithList(List list), вы потребляете список, поэтому вам потребуется ковариация / расширение (или инвариантный список). С другой стороны, если ваш метод List doSomethingProvidingList, тогда вы создаете список, и вам потребуется контравариантность / супер (или инвариантный список). - person Raman; 24.01.2014
comment
@MichaelMyers: Почему мы не можем просто использовать параметризованный тип для обоих этих случаев? Есть ли здесь какое-то конкретное преимущество использования подстановочных знаков или это просто средство улучшения читабельности, подобное, скажем, использованию ссылок на const в качестве параметров метода в C ++, чтобы указать, что метод не изменяет аргументы? - person Chatterjee; 24.05.2014
comment
@Raman, я думаю, ты только что напутал. В doSthWithList (у вас может быть List ‹? Super Thing›), поскольку вы являетесь потребителем, вы можете использовать super (помните, CS). Однако это Список ‹? extends Thing ›getList (), поскольку вам разрешено возвращать что-то более конкретное при создании (PE). - person masterxilo; 27.05.2014
comment
что, если мы хотим пройти и добавить в коллекцию? - person H.Rabiee; 23.10.2014
comment
@hajder Тогда единственный вариант - использовать точный тип: Collection<Thing>. Причина этого в том, что вы должны быть уверены, что метод get вернет то, что вы можете преобразовать в Thing, а метод add принимает Thing, который он может преобразовать в фактический тип элемента. Единственный тип, который удовлетворяет обоим критериям, - это сам Thing. - person biziclop; 18.05.2015
comment
@hajder Обратите внимание, что здесь предполагается, что вам действительно нужно преобразовать элементы в Thing. - person biziclop; 18.05.2015
comment
@Chatterjee: причина использования подстановочных знаков - гибкость. Объявив, что параметр имеет тип Collection ‹? super Thing ›вы даете вызывающей стороне большую гибкость, поскольку она может вызывать ваш метод не только с Collection ‹Thing› в качестве аргумента, но также с Collection ‹SomeSupertypeOfThing› в качестве аргумента. - person Janus Varmarken; 11.02.2016
comment
@MichaelMyers: Для case2 Collection <? super Thing> тоже можно повторять, верно? - person Eric Zhang; 30.05.2017
comment
@EricZhang: Когда вы добавляете в коллекцию, не имеет значения, является ли коллекция итерируемой. - person Michael Myers; 01.06.2017
comment
PECS противоречит интуиции. Если я беру что-то из списка, значит, я потребляю или производю? - person AZ_; 30.05.2018
comment
@AZ_ Я разделяю ваше мнение. Если метод получает () из списка, метод будет считаться Потребителем ‹T›, а список считается поставщиком; но правило PECS - «с точки зрения списка», поэтому необходимо «расширять». Это должно быть GEPS: get extends; поставил супер. - person Treefish Zhang; 04.05.2019
comment
«Если вы делаете и то, и другое с одной и той же коллекцией, вам не следует использовать extends или super». Кто-нибудь может более подробно объяснить эту цитату? Что вы должны использовать вместо этого, если хотите сделать и то, и другое с одной и той же коллекцией? - person Aiyuni; 02.08.2019
comment
@Aiyuni границы подстановочного знака включают тип T в конце границы. Например: <? super T> включает T и все его супертипы, и аналогично <? extends T> включает T и все его подтипы. Верхняя и нижняя границы несовместимы в одной и той же коллекции - одна описывает все типы, включая и ниже объявленного типа, а другая - все типы выше и включая объявленный тип. Итак, изюминка: для Collection, который должен и производить, и потреблять T, объявление должно быть просто Collection<T> - person mouselabs; 17.04.2020

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

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

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

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

person anoopelias    schedule 02.11.2013
comment
Привет всем. Я Андрей Тюкин, я просто хотел подтвердить, что anoopelias & DaoWen связались со мной и получили мое разрешение на использование скетча, он под лицензией (CC) -BY-SA. Спасибо @ Anoop за то, что дал ему вторую жизнь ^^ @Brian Agnew: (несколько голосов): Это потому, что это набросок для Scala, он использует синтаксис Scala и предполагает вариацию сайта объявления, что сильно отличается от странного сайта вызова Java дисперсия ... Может быть, мне стоит написать более подробный ответ, который ясно показывает, как этот скетч применим к Java ... - person Andrey Tyukin; 16.06.2014
comment
Это одно из самых простых и ясных объяснений ковариации и контравариантности, которые я когда-либо находил! - person cs4r; 01.05.2017
comment
@ Андрей Тюкин Привет, я тоже хочу использовать это изображение. Как я могу связаться с вами? - person slouc; 02.06.2017
comment
Если у вас есть какие-либо вопросы по поводу этой иллюстрации, мы можем обсудить их в чате: chat.stackoverflow.com/rooms / 145734 / - person Andrey Tyukin; 02.06.2017
comment
Классная графика, хотя я не понимаю обозначений: Потребитель всего ‹Потребитель овощей. А с другой стороны: Продюсер развлечений ›Продюсер музыки. Разве это не должно быть отменено? - person Dmitry Ziyatdinov; 13.08.2017
comment
Может кто-нибудь объяснить использование / применение ‹? супер MyClass ›. Потому что вы можете поместить в него MyClass и его объекты подкласса, но если вы хотите извлечь что-то из этой коллекции. Их можно вынести только как Объекты. - person Chetan Gowda; 21.05.2018
comment
@ChetanGowda Вот довольно подробный пример, объясняющий, почему <? super X> полезен и что произойдет, если вы его опустите: Java 8 Comparator comparing() статическая функция < / а>. - person Andrey Tyukin; 11.08.2018
comment
Угловые скобки являются направленными или сравнительными? - person Eric; 20.05.2020

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

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

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

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

Пример на Java:

class Super {
        Number testCoVariance() {
            return null;
        }
        void testContraVariance(Number parameter) {
        } 
    }
    
    class Sub extends Super {
        @Override
        Integer testCoVariance() {
            return null;
        } //compiles successfully i.e. return type is don't care(Integer is subtype of Number)
        @Override
        void testContraVariance(Integer parameter) {
        } //doesn't support even though Integer is subtype of Number
    }

Принцип подстановки Лискова (LSP) гласит, что «объекты в программе должны заменяться экземплярами их подтипов без изменения правильности этой программы».

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

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

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

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

Чтобы проиллюстрировать это общее явление, рассмотрим тип массива. Для типа 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 разных вида подстановочных знаков:

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

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

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

Ковариация и контравариантность определяют совместимость на основе по типам. В любом случае дисперсия - это направленное отношение. Ковариация может быть переведена как разные в одном направлении или с разными, тогда как контравариантность означает разные в противоположном направлении или против разных. Ковариантный и контравариантный типы - это не одно и то же, но между ними существует взаимосвязь. Имена подразумевают направление корреляции.

https://stackoverflow.com/a/54576828/1697099
https://stackoverflow.com/a/64888058/1697099

  • Ковариация: принять подтипы (только чтение, т. Е. Производитель)
  • Контравариантность: принимать супертипы (только писать, т. Е. Потребитель)
person Premraj    schedule 26.07.2015
comment
Эй, я просто хотел знать, что вы имели в виду в последнем предложении: если вы думаете, что моя аналогия неверна, обновите. Вы имеете в виду, если это неправильно с этической точки зрения (что субъективно) или это неправильно в контексте программирования (что объективно: нет, это не так)? Я хотел бы заменить его более нейтральным примером, который является универсально приемлемым, независимо от культурных норм и этических убеждений; Если это вас устраивает. - person Neuron; 29.04.2018
comment
наконец-то я смог это понять. Хорошее объяснение. - person Oleg Kuts; 12.04.2019
comment
@Premraj, In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put., я не могу добавить элемент в Список ‹?› Или Список ‹? extends Object ›, поэтому я не понимаю, почему это может быть Use when you both get and put. - person LiuWenbin_NO.; 17.05.2019
comment
@LiuWenbin_NO. - Эта часть ответа вводит в заблуждение. ? - неограниченный подстановочный знак - соответствует полной противоположности инвариантности. См. Следующую документацию: docs.oracle.com/javase/tutorial / java / generics /, в котором говорится: В случае, когда коду требуется доступ к переменной как в качестве входной, так и выходной переменной, не используйте подстановочный знак. (Они используются в и out как синоним "получить и положить"). За исключением null, вы не можете добавить в Коллекцию, параметризованную с помощью ?. - person mouselabs; 16.04.2020
comment
stackoverflow.com/a/1368212/1697099 для получения дополнительной информации. - person Premraj; 17.06.2021

Вкратце, три простых правила, которые нужно запомнить PECS:

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

Как я объясняю в моем ответе на другой вопрос, PECS - это мнемоническое устройство, созданное Джошем Блохом, чтобы помочь запомнить P разработчик extends, C потребителя 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 в качестве параметра типа для универсального возвращаемого типа.

person ColinD    schedule 27.04.2010
comment
Этот принцип применим только к коллекциям? Это имеет смысл, когда кто-то пытается соотнести его со списком. Если вы думаете о сигнатуре сортировки (Список ‹T›, Comparator ‹? Super T›) --- ›здесь Comparator использует super, поэтому это означает, что он является потребителем в контексте PECS. Например, если вы посмотрите на реализацию, например: public int compare (Person a, Person b) {return a.age ‹b.age? -1: a.age == b.age? 0: 1; } Мне кажется, что Person ничего не потребляет, но производит возраст. Это меня смущает. Есть ли в моих рассуждениях изъян или PECS действует только для коллекций? - person Fatih Arslan; 07.02.2019
comment
@FatihArslan не разбирается в реализации компаратора. Это не имеет значения. Метод sort(List<T>,Comparator<? super T>) объявляет границы типа, и в этом sort методе компаратор использует T экземпляры. - person Holger; 08.06.2021

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

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

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

Подводя итоги

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

person Daniel    schedule 23.09.2018

Это самый ясный и простой способ подумать о расширениях и супер:

  • extends предназначен для чтения

  • super предназначен для письма

Я считаю, что «PECS» - неочевидный способ думать о вещах, касающихся того, кто является «производителем», а кто «потребителем». "PECS" определяется с точки зрения самой коллекции данных - коллекция "потребляет", если в нее записываются объекты (она потребляет объекты из вызывающего кода), и он «производит», если объекты считываются из (он создает объекты для некоторого вызывающего кода). Это противоречит тому, как все остальное названо. Стандартные API Java именуются с точки зрения вызывающего кода, а не самой коллекции. Например, ориентированный на коллекцию вид java.util. В списке должен быть метод с именем «receive ()» вместо «add ()» - в конце концов, вызывающий код добавляет элемент, но сам список получает элемент.

Я думаю, что более интуитивно, естественно и последовательно думать о вещах с точки зрения кода, который взаимодействует с коллекцией - «читает» ли код или «записывает» в коллекцию? После этого любой код, записывающий в коллекцию, будет «производителем», а любой код, считывающий коллекцию, будет «потребителем».

person kaan    schedule 22.01.2020
comment
Я столкнулся с тем же мысленным столкновением и склонен согласиться, за исключением того, что PECS не определяет именование кода, а сами границы типов устанавливаются в объявлениях Collection. Более того, что касается именования, у вас часто есть имена для создания / потребления Коллекций, такие как src и dst. Итак, вы имеете дело и с кодом, и с контейнерами одновременно, и в итоге я подумал об этом в соответствии с этими принципами - потребление кода из производящего контейнера и создание кода для потребляющего контейнера. - person mouselabs; 17.04.2020

(добавление ответа, потому что никогда не бывает достаточно примеров с подстановочными знаками 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);
        }
    }
person Andrejs    schedule 17.05.2017

«Правило» PECS просто гарантирует, что следующее является законным:

  • Потребитель: чем бы ни был ?, он может юридически относиться к T
  • Производитель: кем бы ни был ?, на него может ссылаться T

Типичное сочетание строк List<? extends T> producer, List<? super T> consumer просто гарантирует, что компилятор может применять стандартные правила отношения наследования «IS-A». Если бы мы могли сделать это на законных основаниях, было бы проще сказать <T extends ?>, <? extends T> (или еще лучше в Scala, как вы можете видеть выше, это [-T], [+T]. К сожалению, лучшее, что мы можем сделать, это <? super T>, <? extends T>.

Когда я впервые столкнулся с этим и сломал это в своей голове, механика имела смысл, но сам код продолжал сбивать меня с толку - я продолжал думать: «Похоже, что границы не нужно так инвертировать» - хотя я было ясно из вышеизложенного - что речь идет просто о гарантии соблюдения стандартных правил использования.

Мне помогло то, что я посмотрел на это, используя обычное назначение в качестве аналогии.

Рассмотрим следующий код игрушки (не готовый к производству):

// copies the elements of 'producer' into 'consumer'
static <T> void copy(List<? extends T> producer, List<? super T> consumer) {
   for(T t : producer)
       consumer.add(t);
}

Иллюстрируя это с точки зрения аналогии с присваиванием, для consumer подстановочный знак ? (неизвестный тип) является ссылкой - «левая сторона» присвоения - и <? super T> гарантирует, что независимо от того, что ?, T "IS-A" ? - это T может быть назначен ему, потому что ? является супертипом (или, самое большее, тем же типом), что и T.

Для producer проблема такая же, она просто инвертирована: подстановочный знак producer ? (неизвестный тип) - это референт - "правая часть" присвоения - и <? extends T> гарантирует, что независимо от того, что ?, ? "ЕСТЬ -A "T - это это может быть назначено на T, потому что ? является подтипом (или, по крайней мере, тем же типом), что и T.

person mouselabs    schedule 18.04.2020
comment
Для тех, кто, как я, не понимал терминологию IS-A: en.wikipedia.org/ wiki / Is-a - person Michal Vician; 12.06.2020
comment
@MichalVician Представьте себе class A и class B, для каждого из которых определен единственный общедоступный метод - a() и b() - соответственно. Если B extends A, то результат таков, что B содержит как a(), так и b(). B, затем IS-A A, потому что он полностью представляет интерфейс A. Но то же самое нельзя сказать о A - A не B, мы знаем только, что B (по крайней мере) A, так как extends A - person mouselabs; 19.06.2020

давайте попробуем визуализировать эту концепцию.

<? super SomeType> является «неопределенным (пока)» типом, но этот неопределенный тип должен быть суперклассом класса SomeType.

То же самое и с <? extends SomeType>. Это тип, который должен расширять класс SomeType (он должен быть дочерним классом класса SomeType).

Если мы рассмотрим концепцию «наследования классов» на диаграмме Венна, пример будет таким:

введите описание изображения здесь

Класс Mammal расширяет класс Animal (класс Animal является суперклассом класса Mammal).

Класс Cat / Dog расширяет класс Mammal (класс Mammal - это супер класс класса Cat / Dog).

Затем давайте представим «круги» на приведенной выше диаграмме как «коробку», имеющую физический объем.

введите описание изображения здесь

НЕЛЬЗЯ поместить большую коробку в меньшую.

Вы можете вставлять ТОЛЬКО меньшую коробку в большую.

Когда вы говорите <? super SomeType>, вы хотите описать "коробку" того же размера или больше, чем поле SomeType.

Если вы говорите <? extends SomeType>, то вы хотите описать "поле" того же размера или меньше, чем поле SomeType.

Так что же такое PECS?

Примером «Продюсера» является Список, из которого мы только читаем.

Примером «Потребителя» является Список, в который мы только записываем.

Имейте в виду следующее:

  • Мы «читаем» от «продюсера» и берем этот материал в свою коробку.

  • И мы «вписываем» свою коробку в «потребителя».

Итак, нам нужно прочитать (взять) что-то от «производителя» и поместить это в нашу «коробку». Это означает, что любые коробки, взятые у производителя, НЕ больше, чем наша «коробка». Вот почему "P roducer E xtends".

«Расширяется» означает меньшую рамку (меньший кружок на диаграмме Венна выше). Ящики производителя должны быть меньше, чем наша собственная, потому что мы заберем эти ящики у производителя и поместим их в свою. Ничего большего, чем наша коробка, поставить нельзя!

Кроме того, нам нужно написать (поместить) нашу собственную «коробку» в «потребителя». Это означает, что коробки потребителя НЕ должны быть меньше, чем наша собственная. Вот почему «C потребителя S uper».

«Супер» означает большую рамку (больший кружок на диаграмме Венна выше). Если мы хотим поместить наши собственные коробки в потребителя, коробки потребителя должны быть больше, чем наша коробка!

Теперь мы можем легко понять этот пример:

public class Collections { 
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      for (int i = 0; i < src.size(); i++) 
        dest.set(i, src.get(i)); 
  } 
}

В приведенном выше примере мы хотим прочитать (взять) что-то из src и записать (поместить) это в dest. Итак, src - это «Производитель», и его «коробки» должны быть меньше (точнее), чем какой-то тип T.

И наоборот, dest является «Потребителем», и его «коробки» должны быть больше (более общие), чем какой-либо тип T.

Если бы «коробки» у src были больше, чем у dest, мы не могли бы поместить эти большие коробки в меньшие коробки, которые есть у dest.

Если кто-нибудь прочитает это, я надеюсь, что это поможет вам лучше понять «P roducer E xtends, C onsumer S uper . »

Удачного кодирования! :)

person starriet    schedule 18.11.2020
comment
лучший ответ, который я когда-либо видел - person scottxiao; 11.05.2021

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

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

person Farrukh Chishti    schedule 07.02.2019

Помните это:

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

person Jason    schedule 19.02.2018

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

Давайте посмотрим на пример

public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }

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

//ListA
List<A> listA = new ArrayList<A>();

//add
listA.add(new A());
listA.add(new B());
listA.add(new C());

//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListB
List<B> listB = new ArrayList<B>();

//add
listB.add(new B());

//get
B b0 = listB.get(0);

Проблема

Поскольку Коллекция Java является ссылочным типом, в результате возникают следующие проблемы:

Проблема №1

//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;

* Общий Swift не имеет такой проблемы, потому что Collection - это Value type [About], поэтому новая коллекция создано

Проблема №2

//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;

Решение - общие подстановочные знаки

Подстановочный знак - это функция ссылочного типа, и его нельзя создать напрямую.

Решение №1 <? super A>, также известное как нижняя граница или контравариантность, также известная как потребители, гарантирует, что оно управляется A и всеми суперклассами, поэтому безопасно добавить

List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();

//add
listSuperA.add(new A());
listSuperA.add(new B());

//get
Object o0 = listSuperA.get(0);

Решение №2

<? extends A>, также известная как верхняя граница, также известная как ковариация, или производители, гарантирует, что она управляется A и всеми подклассами, поэтому можно безопасно получить и преобразовать

List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;

//get
A a0 = listExtendsA.get(0);
person yoAlex5    schedule 03.06.2020

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

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

person    schedule
comment
Так ? extends B следует интерпретировать как? B расширяется. Это то, что B расширяет, чтобы включить все суперклассы B до Object, за исключением самого B. Спасибо за код! - person Saurabh Patil; 30.05.2016
comment
@SaurabhPatil Нет, ? extends B означает B и все, что расширяет B. - person asgs; 28.09.2016