Вопрос

Я вошел через УИК (короткие для Режиссер extends и потребитель super) во время чтения на дженерах.

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

Это было полезно?

Решение

TL; DR: «PEC» - с точки зрения сбора. Если ты Только Вытягивание предметов из общей коллекции, это производитель, и вы должны использовать 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 гарантии.

Другие советы

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

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

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

Covariance vs Contravariance

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

}

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

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

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

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

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

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

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

Примеры 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-variace / Неприемник: ? или ? extends Object - Неограниченный Подстановочный знак. Это означает семью всех типов. Используйте, когда вы оба получите и поставьте.
  • Ко-дисперсия: ? extends T (семья всех типов, которые являются подтипами T) - подстановочный знак с верхняя граница. T это верх-мический класс в иерархии наследования. Использовать АН extends подстановочный знак, когда вы только Получать значения из структуры.
  • Contra-variance: ? 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.
        */  
    }
}

универсал а также Примеры

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

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

В двух словах, три легких правила, чтобы помнить PECS:

  1. Использовать <? extends T> Подстановочный знак Если вам нужно получить объект типа T из коллекции.
  2. Использовать <? super T> Подстановочный знак Если вам нужно поставить объекты типа T в коллекции.
  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 - продюсер расширяется:

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 будет подтип или Supertype типа списка. При выполнении типа списка может быть любой из типов A, B, C. Таким образом, вы не можете в конечном итоге добавить Hammerskark (Super Type) в списке Deadhammershark, например.

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

Давайте уточним CS - потребитель Супер:

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

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

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

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

Вы можете добавить вышеуказанные типы объектов, потому что все ниже акулы (A, B, C) всегда будут подтипы чего-либо выше, выше акулы (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 из списка. Мы не можем добавить элемент к нему.

Помните это:

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

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

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

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

  1. Представьте себе грузовой поезд с грузовыми автомобилями как аналогии с списком.
  2. Ты сможешь помещать груз в грузовом автомобиле, если груз имеет такой же или меньший размер чем грузовой автомобиль = <? super FreightCarSize>
  3. Ты сможешь разгружать груз от грузовой машины, если у вас есть Достаточно места (больше, чем размер груза) в вашем депо = <? extends DepotSize>
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top