Что такое PECS (продюсер продвигает Consumer Super)?

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

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

18 ответов

Решение

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

Принципы, лежащие в основе этого в области компьютерных наук, названы в честь

  • Ковариантность -? расширяет MyClass,
  • Контравариантность -? супер MyClass и
  • Инвариантность / не-дисперсия - MyClass

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

Фото предоставлено Andrey Tyukin

Ковариантность против Контравариантности

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

Принцип "получи и положи" (из обобщений и коллекций Java)

Говорится,

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

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

1. Для расширяет подстановочный знак (получить значения, например, производитель 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 )

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

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

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

PECS (Производитель 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 
}

Принцип подстановки Лискова:массивы являются ковариантными (небезопасными), но обобщенные не являются неизменными (безопасными). т.е. принцип замещения не работает с параметризованными типами, что означает, что запись запрещена.
Ковариант просто означает, что если 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 различных вида подстановочных знаков:

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

Примечание: подстановочный знак ? означает ноль или один раз, представляет неизвестный тип. Подстановочный знак можно использовать как тип параметра, никогда не использовать в качестве аргумента типа для вызова универсального метода, создания экземпляра универсального класса (т. Е. Когда используется подстановочный знак, ссылка на который не используется в других частях программы, как мы используем T)

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.
        */  
    }
}

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

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

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

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

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

<? super SomeType>является типом "undefined(пока)", но этот неопределенный тип должен быть суперклассом класса 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 onsumer 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".

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

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

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

Подводить итоги

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

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

  • extendsдля чтения

  • superдля написания

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

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

"Правило" 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 "ЭТО" ? - что T можно присвоить ему, потому что ? является супертипом (или в лучшем случае того же типа), что и T.

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

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

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

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

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

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

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

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

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

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

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

PECS: производитель расширяется, а потребитель супер

Предпосылки для понимания:

  • Общие и общие подстановочные знаки
  • Полиморфизм, подтипы и супертипы

Допустим, у нас есть тип, который принимает общий параметр типа T, например. Когда мы пишем код, может быть потенциально полезно также разрешить подтипы или супертипы нашего универсального параметра типа T. Это ослабляет ограничения для пользователя API и может сделать код более гибким.

Давайте сначала посмотрим, что мы получим, сняв эти ограничения. Допустим, у нас есть следующие 3 класса:

      class BaseAnimal{};

class Animal extends BaseAnimal{};

class Duck extends Animal{};

и мы создаем общедоступный метод, который принимает list<Animal>

  1. Если мы используем super вместо теперь, мы можем передать больше списков, чтобы удовлетворить требования нашего метода. Теперь мы можем пройти либо, либо даже
  2. Если мы используем extends вместо теперь мы можем передать больше списков, чтобы удовлетворить требования нашего метода. Теперь мы можем перейти либо в, либо

Однако это накладывает следующие 2 ограничения:

  1. Если мы используем супер тип, например мы не знаем точного типа это будет. Это может быть список или или же . У нас нет возможности узнать. Это означает, что мы никогда не сможем получить значение из этого списка, потому что мы не знаем, какой будет тип. Однако мы можем поместить любой тип данных, который является или расширяет его, в файл . Поскольку мы можем только помещать данные в хранилище, оно называется потребителем данных.
  2. Если мы используем расширение вместо . Мы также не знаем, какой именно тип. Это может быть либо или же . Мы не можем добавить что-то к настоящему, потому что мы никогда не можем точно знать, что это за тип. Однако мы можем вытащить что-то, потому что мы всегда знаем, что все, что выходит из списка, является подтипом . Поскольку мы можем получить данные только из это называется производитель данных.

Вот простая программа, иллюстрирующая ослабление ограничений типа:

      import java.util.ArrayList;
import java.util.List;

public class Generics {
    public static void main(String[] args) {

        Generics generics = new Generics();

        generics.producerExtends(new ArrayList<Duck>());
        generics.producerExtends(new ArrayList<Animal>());

        generics.consumerSuper(new ArrayList<Object>());
        generics.consumerSuper(new ArrayList<Animal>());

    }

    //  ? extends T   is an upper bound
    public void producerExtends (List<? extends Animal> list) {

        // Following are illegal since we never know exactly what type the list will be
        // list.add(new Duck());
        // list.add(new Animal());
        
        // We can read from it since we are always getting an Animal or subclass from it
        // However we can read them as an animal type, so this compiles fine
        if (list.size() > 0) {
            Animal animal = list.get(0);
        }
    }

    // ? extends T   is a lower bound
    public void consumerSuper (List<? super Animal> list) {
        // It will be either a list of Animal or a superclass of it
        // Therefore we can add any type which extends animals
        list.add(new Duck());
        list.add(new Animal());

        // Compiler won't allow this it could potentially be a super type of Animal
        // Animal animal = list.get(0);
    }

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

              - 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.
Другие вопросы по тегам