Что такое УЭК (производитель расширяет потребительский супер)?


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

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

11 570

11 ответов:

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


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

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

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

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

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

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

  • ковариация - ? расширяет класса MyClass
  • контравариантность - ? супер MyClass и
  • инвариантность / не-дисперсия-MyClass

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

Фото предоставлено : Андрей Тюкин

Covariance vs Contravariance

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

принцип Get и Put (из Java Generics и коллекций)

в нем говорится,

  1. использовать расширяет подстановочный знак когда вы только get значения из структуры
  2. использовать супер подстановочный знак когда вы только поставить значения в a структура
  3. и не используйте подстановочный знак когда вы как получить, так и поставить.

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

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

List<Object> objs = Arrays.<Object>asList(1,"two");
List<? super Integer> ints = objs;
String str = "";
for (Object obj : ints) str += obj.toString();
assert str.equals("1two");

3. Когда и Get и Put, не используйте подстановочный знак

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

public static double sumCount(Collection<Number> nums, int n) {
   count(nums, n);
   return sum(nums);
}
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
    }
}

Печ (производитель 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 различных вкуса подстановочных знаков:

  • в дисперсии/без отклонений: ? или ? extends Object -неограниченная символ. Оно стоит для семьи всех типов. Используйте, когда вы оба получаете и ставите.
  • ковариационный: ? extends T (семейство всех типов, которые являются подтипами T) - подстановочный знак с верхний предел. T - это верхний-большинство классов в иерархии наследования. Используйтеextends подстановочный знак, когда вы только Get значения из структуры.
  • Contra-variance:? super T ( семьи всех типов, которые являются супертипами T) - подстановочный знак с нижняя граница. T - это ниже-большинство классов в иерархии наследования. Используйте super подстановочный знак, когда вы только поставить значения в a структура.

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

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

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

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

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

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

в двух словах легко запомнить УИК

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

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

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

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

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

sharks.add(new HammerShark());

...но он не может сказать, будет ли во время выполнения ваш B подтипом или супертипом типа списка. Во время выполнения тип списка может быть любым из типов A, B, C. Поэтому вы не можете добавить HammerSkark (super type) в список 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). Легкий для понимания.

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

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

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