Разница между <? супер T> и <? расширяет T> в Java

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

Я использовал List<? extends T>, но он не позволяет добавлять к нему элементы list.add(e), тогда как List<? super T> делает.

Ответ 1

extends

Подстановочное объявление List<? extends Number> foo3 означает, что любое из них является юридическим назначением:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>();  // Double extends Number
  • Чтение. С учетом вышеперечисленных назначений, какой тип объекта вы гарантированно прочитаете из List foo3:

    • Вы можете прочитать Number, потому что любой из списков, которые могут быть назначены foo3, содержит Number или подкласс Number.
    • Вы не можете прочитать Integer, потому что foo3 может указывать на List<Double>.
    • Вы не можете прочитать Double, потому что foo3 может указывать на List<Integer>.
  • Написание. С учетом вышеперечисленных назначений, какой тип объекта вы могли бы добавить в List foo3, который был бы законным для всех выше возможных ArrayList присвоения:

    • Вы не можете добавить Integer, потому что foo3 может указывать на List<Double>.
    • Вы не можете добавить Double, потому что foo3 может указывать на List<Integer>.
    • Вы не можете добавить Number, потому что foo3 может указывать на List<Integer>.

Вы не можете добавить какой-либо объект в List<? extends T>, потому что вы не можете гарантировать, какой тип List он действительно указывает, поэтому вы не можете гарантировать, что объект разрешен в List. Единственная "гарантия" заключается в том, что вы можете читать только ее, и вы получите T или подкласс T.

super

Теперь рассмотрим List <? super T>.

Подстановочное объявление List<? super Integer> foo3 означает, что любое из них является юридическим назначением:

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>();   // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>();   // Object is a superclass of Integer
  • Чтение. С учетом вышеперечисленных назначений, какой тип объекта вы гарантируете получить при чтении из List foo3:

    • Вам не гарантируется Integer, потому что foo3 может указывать на List<Number> или List<Object>.
    • Вам не гарантируется Number, потому что foo3 может указывать на List<Object>.
    • Гарантия только заключается в том, что вы получите экземпляр Object или подкласса Object (но вы не знаете, какой подкласс).
  • Написание. С учетом вышеперечисленных назначений, какой тип объекта вы могли бы добавить в List foo3, который был бы законным для всех выше возможных ArrayList присвоения:

    • Вы можете добавить Integer, потому что Integer разрешен в любом из перечисленных выше списков.
    • Вы можете добавить экземпляр подкласса Integer, потому что экземпляр подкласса Integer разрешен в любом из приведенных выше списков.
    • Вы не можете добавить Double, потому что foo3 может указывать на ArrayList<Integer>.
    • Вы не можете добавить Number, потому что foo3 может указывать на ArrayList<Integer>.
    • Вы не можете добавить Object, потому что foo3 может указывать на ArrayList<Integer>.

PECS

Помните PECS: "Продвижение продюсера, потребительский супер" .

  • "Producer Extends" . Если вам нужен List для создания значений T (вы хотите прочитать T из списка), вам нужно объявить его ? extends T, например List<? extends Integer>. Но вы не можете добавить в этот список.

  • "Потребительский супер" . Если вам нужен List для использования значений T (вы хотите записать T в список), вам нужно объявить его ? super T, например List<? super Integer>. Но нет никаких гарантий того, какой тип объекта вы можете прочитать из этого списка.

  • Если вам нужно как читать, так и записывать в список, вам нужно объявить его точно без каких-либо подстановочных знаков, например. List<Integer>.

Пример

Примечание этот пример из часто задаваемых вопросов Java Generics. Обратите внимание, что в списке источников src (в списке производителей) используется extends, а в списке адресатов dest (список потребляющих) используется super:

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

Также см. Как добавить в List <? расширяет число > структуры данных?

Ответ 2

Представьте себе, что эта иерархия

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

1. Расширяет

Написав

    List<? extends C2> list;

вы говорите, что list сможет ссылаться на объект типа (например) ArrayList, общий тип которого является одним из подтипов 7 C2 (C2 включено):

  • C2: new ArrayList<C2>();, (объект, который может хранить C2 или подтипы) или
  • D1: new ArrayList<D1>();, (объект, который может хранить D1 или подтипы) или
  • D2: new ArrayList<D2>();, (объект, который может хранить D2 или подтипы) или...

и т.д. Семь различных случаев:

    1) new ArrayList<C2>(): can store C2 D1 D2 E1 E2 E3 E4
    2) new ArrayList<D1>(): can store    D1    E1 E2  
    3) new ArrayList<D2>(): can store       D2       E3 E4
    4) new ArrayList<E1>(): can store          E1             
    5) new ArrayList<E2>(): can store             E2             
    6) new ArrayList<E3>(): can store                E3             
    7) new ArrayList<E4>(): can store                   E4             

У нас есть набор "сохраняемых" типов для каждого возможного случая: 7 (красный) устанавливает здесь графически представленный

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

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

  • вы не можете list.add(new C2(){});, потому что это может быть list = new ArrayList<D1>();
  • вы не можете list.add(new D1(){});, потому что это может быть list = new ArrayList<D2>();

и т.д.

2. Супер

Написав

    List<? super C2> list;

вы говорите, что list сможет ссылаться на объект типа (например) ArrayList, общий тип которого является одним из 7 супертипов C2 (C2 включено):

  • A1: new ArrayList<A1>();, (объект, который может хранить A1 или подтипы) или
  • A2: new ArrayList<A2>();, (объект, который может хранить A2 или подтипы) или
  • A3: new ArrayList<A3>();, (объект, который может хранить A3 или подтипы) или...

и т.д. Семь различных случаев:

    1) new ArrayList<A1>(): can store A1          B1 B2       C1 C2    D1 D2 E1 E2 E3 E4
    2) new ArrayList<A2>(): can store    A2          B2       C1 C2    D1 D2 E1 E2 E3 E4
    3) new ArrayList<A3>(): can store       A3          B3       C2 C3 D1 D2 E1 E2 E3 E4
    4) new ArrayList<A4>(): can store          A4       B3 B4    C2 C3 D1 D2 E1 E2 E3 E4
    5) new ArrayList<B2>(): can store                B2       C1 C2    D1 D2 E1 E2 E3 E4
    6) new ArrayList<B3>(): can store                   B3       C2 C3 D1 D2 E1 E2 E3 E4
    7) new ArrayList<C2>(): can store                            C2    D1 D2 E1 E2 E3 E4

У нас есть набор "сохраняемых" типов для каждого возможного случая: 7 (красный) устанавливает здесь графически представленный

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

Как вы можете видеть, здесь мы имеем семь безопасных типов, которые являются общими для каждого случая: C2, D1, D2, E1, E2, E3, E4.

  • вы можете list.add(new C2(){});, потому что, независимо от типа списка, на который мы ссылаемся, C2 разрешено
  • вы можете list.add(new D1(){});, потому что, независимо от типа списка, на который мы ссылаемся, D1 разрешено

и т.д. Вероятно, вы заметили, что эти типы соответствуют иерархии, начиная с типа C2.

Примечания

Здесь полная иерархия, если вы хотите сделать несколько тестов

interface A1{}
interface A2{}
interface A3{}
interface A4{}

interface B1 extends A1{}
interface B2 extends A1,A2{}
interface B3 extends A3,A4{}
interface B4 extends A4{}

interface C1 extends B2{}
interface C2 extends B2,B3{}
interface C3 extends B3{}

interface D1 extends C1,C2{}
interface D2 extends C2{}

interface E1 extends D1{}
interface E2 extends D1{}
interface E3 extends D2{}
interface E4 extends D2{}

Ответ 3

Мне нравится ответ от @Bert F, но это то, как мой мозг видит это.

У меня есть Х в руке. Если я хочу записать мой X в список, этот Список должен быть либо списком X, либо списком вещей, которые мой X может быть upcast, когда я их пишу, т.е. любой суперкласс X...

List<? super   X>

Если я получу список, и я хочу прочитать X из этого списка, лучше быть списком X или списком вещей, которые могут быть upcast для X, когда я их прочитал, т.е. все, что расширяет X

List<? extends X>

Надеюсь, это поможет.

Ответ 4

На основе ответ Bert F Я хотел бы объяснить свое понимание.

Допустим, что мы имеем 3 класса как

public class Fruit{}

public class Melon extends Fruit{}

public class WaterMelon extends Melon{}

Здесь мы имеем

List<? extends Fruit> fruitExtendedList = …

//Says that I can be a list of any object as long as this object extends Fruit.

Теперь давайте попробуем получить какое-то значение из fruitExtendedList

Fruit fruit = fruitExtendedList.get(position)

//This is valid as it can only return Fruit or its subclass.

Опять попробуем

Melon melon = fruitExtendedList.get(position)

//This is not valid because fruitExtendedList can be a list of Fruit only, it may not be 
//list of Melon or WaterMelon and in java we cannot assign sub class object to 
//super class object reference without explicitly casting it.

То же самое относится к

WaterMelon waterMelon = fruitExtendedList.get(position)

Теперь попробуйте установить некоторый объект в fruitExtendedList

Добавление объекта фруктов

fruitExtendedList.add(new Fruit())

//This in not valid because as we know fruitExtendedList can be a list of any 
//object as long as this object extends Fruit. So what if it was the list of  
//WaterMelon or Melon you cannot add Fruit to the list of WaterMelon or Melon.

Добавление объекта Melon

fruitExtendedList.add(new Melon())

//This would be valid if fruitExtendedList was the list of Fruit but it may 
//not be, as it can also be the list of WaterMelon object. So, we see an invalid 
//condition already.

Наконец, попробуйте добавить объект WaterMelon

fruitExtendedList.add(new WaterMelon())

//Ok, we got it now we can finally write to fruitExtendedList as WaterMelon 
//can be added to the list of Fruit or Melon as any superclass reference can point 
//to its subclass object.

Но подождите, если кто-то решит сделать новый тип Lemon, скажем, для аргументов SaltyLemon как

public class SaltyLemon extends Lemon{}

Теперь fruitExtendedList может быть списком Fruit, Melon, WaterMelon или SaltyLemon.

Итак, наше утверждение

fruitExtendedList.add(new WaterMelon())

недействителен.

В принципе, мы можем сказать, что мы ничего не можем написать в файле fruitExtendedList.

Подводит итоги List<? extends Fruit>

Теперь посмотрим

List<? super Melon> melonSuperList= …

//Says that I can be a list of anything as long as its object has super class of Melon.

Теперь попробуем получить какое-то значение из melonSuperList

Fruit fruit = melonSuperList.get(position)

//This is not valid as melonSuperList can be a list of Object as in java all 
//the object extends from Object class. So, Object can be super class of Melon and 
//melonSuperList can be a list of Object type

Аналогично, дыня, WaterMelon или любой другой объект не могут быть прочитаны.

Но заметьте, что мы можем читать экземпляры типа объекта

Object myObject = melonSuperList.get(position)

//This is valid because Object cannot have any super class and above statement 
//can return only Fruit, Melon, WaterMelon or Object they all can be referenced by
//Object type reference.

Теперь попробуйте установить некоторое значение из melonSuperList.

Добавление объекта типа объекта

melonSuperList.add(new Object())

//This is not valid as melonSuperList can be a list of Fruit or Melon.
//Note that Melon itself can be considered as super class of Melon.

Добавление объекта типа Fruit

melonSuperList.add(new Fruit())

//This is also not valid as melonSuperList can be list of Melon

Добавление объекта типа дыни

melonSuperList.add(new Melon())

//This is valid because melonSuperList can be list of Object, Fruit or Melon and in 
//this entire list we can add Melon type object.

Добавление объекта типа WaterMelon

melonSuperList.add(new WaterMelon())

//This is also valid because of same reason as adding Melon

Чтобы подвести итог, мы можем добавить Melon или его подкласс в melonSuperList и только объект объекта Object.

Ответ 5

super - нижняя грань, а продолжения - верхняя граница.

Согласно http://download.oracle.com/javase/tutorial/extra/generics/morefun.html:

Решение состоит в использовании формы ограниченный подстановочный знак мы еще не видели: подстановочные знаки с нижней границей. синтаксис? super T обозначает неизвестное тип, который является супертипом T (или T сам; помните, что супертип отношение рефлексивно). Это двойное ограниченных подстановочных знаков мы были используя, где мы используем? расширяет T до обозначают неизвестный тип, который является подтип T.

Ответ 6

Использование расширений вы можете получить только из коллекции. Вы не можете в нее вставить. Кроме того, хотя супер позволяет как получить, так и поставить, тип возврата во время получения ? супер T.

Ответ 7

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

baseClassInstance = derivedClassInstance;

Вы можете подумать, что Integer extends Number и что Integer будет делать как <? extends Number>, но компилятор скажет вам, что <? extends Number> cannot be converted to Integer (то есть, на человеческом языке, неправильно, что все, что расширяет число может быть преобразован в Integer):

class Holder<T> {
    T v;
    T get() { return v; }
    void set(T n) { v=n; }
}
class A {
    public static void main(String[]args) {
        Holder<? extends Number> he = new Holder();
        Holder<? super Number> hs = new Holder();

        Integer i;
        Number n;
        Object o;

        // Producer Super: always gives an error except
        //       when consumer expects just Object
        i = hs.get(); // <? super Number> cannot be converted to Integer
        n = hs.get(); // <? super Number> cannot be converted to Number
                      // <? super Number> cannot be converted to ... (but
                      //       there is no class between Number and Object)
        o = hs.get();

        // Consumer Super
        hs.set(i);
        hs.set(n);
        hs.set(o); // Object cannot be converted to <? super Number>

        // Producer Extends
        i = he.get(); // <? extends Number> cannot be converted to Integer
        n = he.get();
        o = he.get();

        // Consumer Extends: always gives an error
        he.set(i); // Integer cannot be converted to <? extends Number>
        he.set(n); // Number cannot be converted to <? extends Number>
        he.set(o); // Object cannot be converted to <? extends Number>
    }
}

hs.set(i); нормально, потому что Integer может быть преобразован в любой суперкласс из Number (а не потому, что Integer является суперклассом Number, что неверно).

EDIT добавил комментарий о Consumer Extends и Producer Super - они не имеют смысла, потому что они не указывают, соответственно, ничего и просто Object. Вам рекомендуется помнить PECS, потому что CEPS никогда не бывает полезной.

Ответ 8

Общие подстановочные знаки предназначены для двух основных потребностей:

Чтение из общей коллекции Вставка в общую коллекцию Существует три способа определения коллекции (переменной) с использованием общих подстановочных знаков. Это:

List<?>           listUknown = new ArrayList<A>();
List<? extends A> listUknown = new ArrayList<A>();
List<? super   A> listUknown = new ArrayList<A>();

List<?> означает список, набранный неизвестному типу. Это может быть List<A>, a List<B>, a List<String> и т.д.

List<? extends A> означает список объектов, которые являются экземплярами class A, или subclasses of A (например, B и C). List<? super A> означает, что список набирается либо в A class, либо в superclass of A.

Подробнее: http://tutorials.jenkov.com/java-generics/wildcards.html

Ответ 9

Когда использовать extends и super

Подстановочные знаки наиболее полезны в параметрах метода. Они обеспечивают необходимую гибкость в интерфейсах методов.

Люди часто путаются, когда использовать расширения и когда использовать суперграницы. Эмпирическое правило - принцип get-put. Если вы получаете что-то из параметризованного контейнера, используйте extends.

int totalFuel(List<? extends Vehicle> list) {
int total = 0;
for(Vehicle v : list) {
    total += v.getFuel();
}
return total;}

Метод totalFuel получает транспортные средства из списка, спрашивает их, сколько топлива у них есть, и вычисляет общее количество. Если вы помещаете объекты в параметризованный контейнер, используйте супер.

int totalValue(Valuer<? super Vehicle> valuer) {
int total = 0;
for(Vehicle v : vehicles) {
    total += valuer.evaluate(v);
}
return total;}

Метод totalValue помещает Транспортные средства в Оценщик. Полезно знать, что extends bound гораздо чаще, чем super.

Ответ 10

Вы можете просмотреть все ответы выше, чтобы понять, почему .add() ограничен '<?>', '<? extends>' и отчасти до '<? super>'.

Но вот вывод всего этого, если вы хотите его запомнить, и не хотите каждый раз изучать ответ:

List<? extends A> означает, что он примет любой List из A и подкласс A. Но вы ничего не можете добавить в этот список. Даже объекты типа A.

List<? super A> означает, что он примет любой список A и суперкласс из A. Вы можете добавлять объекты типа A и его подклассы.

Ответ 11

Список <? extends X> не позволяет добавлять что-либо, кроме нулевого, в список.

Список <? super X> позволяет добавлять все, что есть - X (X или его подкласс), или null.

Ответ 12

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

Нам нужно рассмотреть 2 вещи,

1. Назначение переменной списка

List<? extends X> listvar;

Здесь любой список X или список подклассов X может быть назначен listvar.

List<? extends Number> listvar; listvar = new ArrayList<Number>(); listvar = new ArrayList<Integer>();


List<? super X> listvar;

Здесь любой список X или список суперклассов X может быть назначен listvar.

List<? super Number> listvar; listvar = new ArrayList<Number>(); listvar = new ArrayList<Object>();

2. Выполните операцию чтения или записи в переменной списка

'List<? extends X> listvar;'

Вы можете использовать эту функцию, чтобы принять список в аргументах метода и выполнить любые операции с типом X (Примечание: вы можете читать только объекты типа X из списка).

'List<? super Number> listvar;

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

Ответ 13

Я хотел бы визуализировать эту разницу. Предположим, что имеем:

class A { }
class B extends A { }
class C extends B { }

List<? extends T> List<? extends T> - чтение и назначение:

|-------------------------|-------------------|---------------------------------|
|         wildcard        |        get        |              assign             |
|-------------------------|-------------------|---------------------------------|
|    List<? extends C>    |    A    B    C    |   List<C>                       |
|-------------------------|-------------------|---------------------------------|
|    List<? extends B>    |    A    B         |   List<B>   List<C>             |
|-------------------------|-------------------|---------------------------------|
|    List<? extends A>    |    A              |   List<A>   List<B>   List<C>   |
|-------------------------|-------------------|---------------------------------|

List<? super T> List<? super T> - запись и назначение:

|-------------------------|-------------------|--------------------------------------------|
|         wildcard        |        add        |                   assign                   |
|-------------------------|-------------------|--------------------------------------------|
|     List<? super C>     |    C              |  List<Object>  List<A>  List<B>   List<C>  |
|-------------------------|-------------------|--------------------------------------------|
|     List<? super B>     |    B    C         |  List<Object>  List<A>  List<B>            |
|-------------------------|-------------------|--------------------------------------------|
|     List<? super A>     |    A    B    C    |  List<Object>  List<A>                     |
|-------------------------|-------------------|--------------------------------------------|

Во всех случаях:

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

Ответ 14

                                       |
                                       v

Список <? расширяет X>? может быть либо любым подклассом X либо самим X

Список <? супер X>? может быть либо любым суперклассом X либо самим X

                                      ^
                                      |

Ответ 15

Например, порядок наследования принимается как O> S> T> U> V

Использование расширений ключевого слова,

Верный:

List<? extends T> Object = new List<T>();
List<? extends T> Object = new List<U>();
List<? extends T> Object = new List<V>();

Неправильные:

List<? extends T> Object = new List<S>();
List<? extends T> Object = new List<O>();

супер Ключевое слово:

Верный:

List<? super T> Object = new List<T>();
List<? super T> Object = new List<S>();
List<? super T> Object = new List<O>();

Неправильные:

List<? super T> Object = new List<U>();
List<? super T> Object = new List<V>();

Добавление объекта: List Object = new List();

Object.add(new T()); //error

Но почему ошибка? Давайте рассмотрим возможности инициализации объекта List

List<? extends T> Object = new List<T>();
List<? extends T> Object = new List<U>();
List<? extends T> Object = new List<V>();

Если мы используем Object.add (новый T()); то это будет правильно, только если

List<? extends T> Object = new List<T>(); 

Но есть еще две возможности

List Object = new List(); List Object = new List(); Если мы попытаемся добавить (новый T()) к двум вышеуказанным возможностям, это даст ошибку, потому что T является высшим классом U и V. мы пытаемся добавить объект T [который (новый T())] в список типов U и V. Объект более высокого класса (базовый класс) не может быть передан более низкому классу Object (Sub class).

Из-за дополнительных двух возможностей Java дает вам ошибку, даже если вы используете правильную возможность, поскольку Java не знает, к какому объекту вы обращаетесь. Так что вы не можете добавлять объекты в List Object = new List(); так как есть возможности, которые недействительны.

Добавление объекта: List Object = new List();

Object.add(new T()); // compiles fine without error
Object.add(new U()); // compiles fine without error
Object.add(new V()); // compiles fine without error

Object.add(new S()); //  error
Object.add(new O()); //  error

Но почему ошибка возникает в двух предыдущих? мы можем использовать Object.add (новый T()); только при следующих возможностях,

List<? super T> Object = new List<T>();
List<? super T> Object = new List<S>();
List<? super T> Object = new List<O>();

Если мы попытались использовать Object.add (новый T()) в List Object = new List(); и List Object = new List(); то он даст ошибку. Это связано с тем, что мы не можем добавить объект T [который является новым T()] в List Object = new List(); потому что это объект типа U. Мы не можем добавить объект T [который является новым T()] в U Object, потому что T является базовым классом, а U является подклассом. Мы не можем добавить базовый класс в подкласс и почему возникает ошибка. Это то же самое для другого случая.