В чем разница между <? супер E> и <? расширяет E>?

В чем разница между <? super E> и <? extends E>?

Например, если вы посмотрите класс java.util.concurrent.LinkedBlockingQueue, для конструктора будет следующая подпись:

public LinkedBlockingQueue(Collection<? extends E> c)

и для одного для метода:

public int drainTo(Collection<? super E> c)

Ответ 1

Первое говорит, что это "некоторый тип, который является предком E"; второй говорит, что это "некоторый тип, который является подклассом E". (В обоих случаях E сам в порядке.)

Таким образом, конструктор использует форму ? extends E, поэтому он гарантирует, что, когда он извлекает значения из коллекции, все они будут E или некоторым подклассом (то есть совместимым). Метод drainTo пытается поместить значения в коллекцию, поэтому коллекция должна иметь тип элемента E или суперкласс.

В качестве примера предположим, что у вас есть иерархия классов:

Parent extends Object
Child extends Parent

и a LinkedBlockingQueue<Parent>. Вы можете построить эту передачу в List<Child>, которая будет безопасно скопировать все элементы, поскольку каждый Child является родителем. Вы не можете передать List<Object>, потому что некоторые элементы могут быть несовместимы с Parent.

Аналогично, вы можете слить эту очередь в List<Object>, потому что каждый Parent является Object... но вы не можете слить его в List<Child>, потому что List<Child> ожидает, что все его элементы будут совместим с Child.

Ответ 2

Причины этого основаны на том, как Java реализует дженерики.

Пример массивов

С массивами вы можете это сделать (массивы ковариантны)

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

Но что произойдет, если вы попытаетесь это сделать?

myNumber[0] = 3.14; //attempt of heap pollution

Эта последняя строка будет компилироваться просто отлично, но если вы запустите этот код, вы можете получить ArrayStoreException. Потому что вы пытаетесь поместить двойной массив в целочисленный массив (независимо от того, чтобы получить доступ через ссылку на число).

Это означает, что вы можете обмануть компилятор, но вы не можете обмануть систему типа времени выполнения. И это потому, что массивы - это то, что мы называем повторно идентифицируемыми типами. Это означает, что во время выполнения Java знает, что этот массив фактически был создан как массив целых чисел, к которому просто обращаются через ссылку типа Number[].

Итак, как вы можете видеть, одна вещь - это фактический тип объекта, а другая вещь - тип ссылки, которую вы используете для доступа к ней, правильно?

Проблема с Java Generics

Теперь проблема с типичными типами Java заключается в том, что информация о типе отбрасывается компилятором, и она недоступна во время выполнения. Этот процесс называется type erasure. Есть хорошая причина для реализации таких обобщений в Java, но это долгая история, и она связана с бинарной совместимостью с уже существующим кодом.

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

Например,

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap pollution

Если компилятор Java не останавливает вас от этого, система типов времени выполнения также не сможет вас остановить, потому что во время выполнения нет способа определить, что этот список должен быть только списком целых чисел. Java runtime позволит вам помещать все, что вы хотите в этот список, когда он должен содержать только целые числа, потому что когда он был создан, он был объявлен как список целых чисел.

Таким образом, разработчики Java убедились, что вы не можете обмануть компилятор. Если вы не можете обмануть компилятор (как мы можем это сделать с массивами), вы не сможете обмануть систему типов времени выполнения.

Как таковые, мы говорим, что общие типы невосстанавливаются.

Очевидно, это затруднит полиморфизм. Рассмотрим следующий пример:

static long sum(Number[] numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Теперь вы можете использовать его следующим образом:

Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};

System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));

Но если вы попытаетесь реализовать тот же код с общими коллекциями, вам это не удастся:

static long sum(List<Number> numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Если вы попытаетесь получить эррос компилятора, вы получите...

List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);

System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error

Решение состоит в том, чтобы научиться использовать две мощные функции Java-дженериков, известные как ковариация и контравариантность.

ковариация

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

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>();
List<? extends Number> myNums = new ArrayList<Double>();

И вы можете читать из myNums:

Number n = myNums.get(0); 

Поскольку вы можете быть уверены, что независимо от того, что содержит фактический список, он может быть увеличен до числа (ведь все, что расширяет Number, является числом, верно?)

Однако вам не разрешено вставлять что-либо в ковариантную структуру.

myNumst.add(45L); //compiler error

Это не будет разрешено, потому что Java не может гарантировать, каков фактический тип объекта в общей структуре. Это может быть все, что расширяет Number, но компилятор не может быть уверен. Таким образом, вы можете читать, но не писать.

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

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

List<Object> myObjs = new List<Object>();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

В этом случае фактическая природа объекта - это список объектов, а через контравариантность вы можете поместить Numbers в него, в основном потому, что все числа имеют Object как их общий предок. Таким образом, все числа являются объектами, и поэтому это действительно.

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

Number myNum = myNums.get(0); //compiler-error

Как вы можете видеть, если компилятор разрешил вам писать эту строку, вы получите исключение ClassCastException во время выполнения.

Принцип Get/Put

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

Самый лучший пример, который у меня есть, - это копирование любых номеров из одного списка в другой список. Он получает только предметы из источника, и он только ставит предметы в судьбу.

public static void copy(List<? extends Number> source, List<? super Number> destiny) {
    for(Number number : source) {
        destiny.add(number);
    }
}

Благодаря силам ковариации и контравариантности это работает для такого случая:

List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);

Ответ 3

<? extends E> определяет E как верхнюю границу: "Это можно отнести к E".

<? super E> определяет E как нижнюю границу: "E может быть применено к этому."

Ответ 4

Я попытаюсь ответить на это. Но чтобы получить действительно хороший ответ, вы должны проверить книгу Джошуа Блоха "Эффективная Java" (2-е издание). Он описывает мнемонический PECS, который означает "продюсер продюсеров, потребительский супер".

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

Итак, например:

public void pushAll(Iterable<? extends E> src) {
  for (E e: src) 
    push(e);
}

И

public void popAll(Collection<? super E> dst) {
  while (!isEmpty())
    dst.add(pop())
}

Но действительно вы должны проверить эту книгу: http://java.sun.com/docs/books/effective/

Ответ 5

Вы можете хотеть google для условий контравариантность (<? super E>) и ковариация (<? extends E>). Я обнаружил, что самая полезная вещь при понимании дженериков заключалась в том, чтобы понять сигнатуру метода Collection.addAll:

public interface Collection<T> {
    public boolean addAll(Collection<? extends T> c);
}

Так же, как вы хотите добавить String в List<Object>:

List<Object> lo = ...
lo.add("Hello")

Вы также можете добавить List<String> (или любую коллекцию String s) с помощью метода addAll:

List<String> ls = ...
lo.addAll(ls)

Однако вы должны понимать, что a List<Object> и a List<String> не являются эквивалентными и не являются последними подклассами первого. Требуется понятие параметра ковариантного типа, т.е. Бит <? extends T>.

Как только у вас есть это, просто подумайте о сценариях, в которых вы также нуждаетесь в контравариантности (проверьте интерфейс Comparable).

Ответ 6

Перед ответом; Пожалуйста, убедитесь, что

  • Функция генерирования только для компиляции, чтобы гарантировать TYPE_SAFETY, она не будет доступна во время RUNTIME.
  • Только ссылка с Generics заставит безопасность типа; если ссылка не объявлена ​​с помощью дженериков, тогда она будет работать без сохранения типа.

Пример:

List stringList = new ArrayList<String>();
stringList.add(new Integer(10)); // will be successful.

Надеюсь, это поможет вам разобраться в шаблоне.

//NOTE CE - Compilation Error
//      4 - For

class A {}

class B extends A {}

public class Test {

    public static void main(String args[]) {

        A aObj = new A();
        B bObj = new B();

        //We can add object of same type (A) or its subType is legal
        List<A> list_A = new ArrayList<A>();
        list_A.add(aObj);
        list_A.add(bObj); // A aObj = new B(); //Valid
        //list_A.add(new String()); Compilation error (CE);
        //can't add other type   A aObj != new String();


        //We can add object of same type (B) or its subType is legal
        List<B> list_B = new ArrayList<B>();
        //list_B.add(aObj); CE; can't add super type obj to subclass reference
        //Above is wrong similar like B bObj = new A(); which is wrong
        list_B.add(bObj);



        //Wild card (?) must only come for the reference (left side)
        //Both the below are wrong;   
        //List<? super A> wildCard_Wrongly_Used = new ArrayList<? super A>();
        //List<? extends A> wildCard_Wrongly_Used = new ArrayList<? extends A>();


        //Both <? extends A>; and <? super A> reference will accept = new ArrayList<A>
        List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<A>();
                        list_4__A_AND_SuperClass_A = new ArrayList<Object>();
                      //list_4_A_AND_SuperClass_A = new ArrayList<B>(); CE B is SubClass of A
                      //list_4_A_AND_SuperClass_A = new ArrayList<String>(); CE String is not super of A  
        List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<A>();
                          list_4__A_AND_SubClass_A = new ArrayList<B>();
                        //list_4__A_AND_SubClass_A = new ArrayList<Object>(); CE Object is SuperClass of A


        //CE; super reference, only accepts list of A or its super classes.
        //List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<String>(); 

        //CE; extends reference, only accepts list of A or its sub classes.
        //List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<Object>();

        //With super keyword we can use the same reference to add objects
        //Any sub class object can be assigned to super class reference (A)                  
        list_4__A_AND_SuperClass_A.add(aObj);
        list_4__A_AND_SuperClass_A.add(bObj); // A aObj = new B();
        //list_4__A_AND_SuperClass_A.add(new Object()); // A aObj != new Object(); 
        //list_4__A_AND_SuperClass_A.add(new String()); CE can't add other type

        //We can't put anything into "? extends" structure. 
        //list_4__A_AND_SubClass_A.add(aObj); compilation error
        //list_4__A_AND_SubClass_A.add(bObj); compilation error
        //list_4__A_AND_SubClass_A.add("");   compilation error

        //The Reason is below        
        //List<Apple> apples = new ArrayList<Apple>();
        //List<? extends Fruit> fruits = apples;
        //fruits.add(new Strawberry()); THIS IS WORNG :)

        //Use the ? extends wildcard if you need to retrieve object from a data structure.
        //Use the ? super wildcard if you need to put objects in a data structure.
        //If you need to do both things, don't use any wildcard.


        //Another Solution
        //We need a strong reference(without wild card) to add objects 
        list_A = (ArrayList<A>) list_4__A_AND_SubClass_A;
        list_A.add(aObj);
        list_A.add(bObj);

        list_B = (List<B>) list_4__A_AND_SubClass_A;
        //list_B.add(aObj); compilation error
        list_B.add(bObj);

        private Map<Class<? extends Animal>, List<? extends Animal>> animalListMap;

        public void registerAnimal(Class<? extends Animal> animalClass, Animal animalObject) {

            if (animalListMap.containsKey(animalClass)) {
                //Append to the existing List
                 /*    The ? extends Animal is a wildcard bounded by the Animal class. So animalListMap.get(animalObject);
                 could return a List<Donkey>, List<Mouse>, List<Pikachu>, assuming Donkey, Mouse, and Pikachu were all sub classes of Animal. 
                 However, with the wildcard, you are telling the compiler that you don't care what the actual type is as long as it is a sub type of Animal.      
                 */   
                //List<? extends Animal> animalList = animalListMap.get(animalObject);
                //animalList.add(animalObject);  //Compilation Error because of List<? extends Animal>
                List<Animal> animalList = animalListMap.get(animalObject);
                animalList.add(animalObject);      


            } 
    }

    }
}

Ответ 7

<? super E> означает any object including E that is parent of E

<? extends E> означает any object including E that is child of E .

Ответ 8

Подстановочный знак с верхней границей выглядит как "? extends Type" и обозначает семейство всех типов, которые являются подтипами типа Type, включая тип Type. Тип называется верхней границей.

Подстановочный знак с нижней границей выглядит как "супер тип" и обозначает семейство всех типов, которые являются супертипами типа, тип Тип включен. Тип называется нижней границей.