В чем разница между List<? super T>
и List<? extends T>
?
Я использовал List<? extends T>
, но он не позволяет добавлять к нему элементы list.add(e)
, тогда как List<? super T>
делает.
В чем разница между List<? super T>
и List<? extends T>
?
Я использовал List<? extends T>
, но он не позволяет добавлять к нему элементы list.add(e)
, тогда как List<? super T>
делает.
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: "Продвижение продюсера, потребительский супер" .
"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 <? расширяет число > структуры данных?
Представьте себе, что эта иерархия
Написав
List<? extends C2> list;
вы говорите, что list
сможет ссылаться на объект типа (например) ArrayList
, общий тип которого является одним из подтипов 7 C2
(C2
включено):
new ArrayList<C2>();
, (объект, который может хранить C2 или подтипы) илиnew ArrayList<D1>();
, (объект, который может хранить D1 или подтипы) или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>();
и т.д.
Написав
List<? super C2> list;
вы говорите, что list
сможет ссылаться на объект типа (например) ArrayList
, общий тип которого является одним из 7 супертипов C2
(C2
включено):
new ArrayList<A1>();
, (объект, который может хранить A1 или подтипы) илиnew ArrayList<A2>();
, (объект, который может хранить A2 или подтипы) или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{}
Мне нравится ответ от @Bert F, но это то, как мой мозг видит это.
У меня есть Х в руке. Если я хочу записать мой X в список, этот Список должен быть либо списком X, либо списком вещей, которые мой X может быть upcast, когда я их пишу, т.е. любой суперкласс X...
List<? super X>
Если я получу список, и я хочу прочитать X из этого списка, лучше быть списком X или списком вещей, которые могут быть upcast для X, когда я их прочитал, т.е. все, что расширяет X
List<? extends X>
Надеюсь, это поможет.
На основе ответ 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.
super - нижняя грань, а продолжения - верхняя граница.
Согласно http://download.oracle.com/javase/tutorial/extra/generics/morefun.html:
Решение состоит в использовании формы ограниченный подстановочный знак мы еще не видели: подстановочные знаки с нижней границей. синтаксис? super T обозначает неизвестное тип, который является супертипом T (или T сам; помните, что супертип отношение рефлексивно). Это двойное ограниченных подстановочных знаков мы были используя, где мы используем? расширяет T до обозначают неизвестный тип, который является подтип T.
Использование расширений вы можете получить только из коллекции. Вы не можете в нее вставить. Кроме того, хотя супер позволяет как получить, так и поставить, тип возврата во время получения ? супер T.
Самая запутанная вещь в том, что любые ограничения типов, которые мы указываем, присваивание работает только одним способом:
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 никогда не бывает полезной.
Общие подстановочные знаки предназначены для двух основных потребностей:
Чтение из общей коллекции Вставка в общую коллекцию Существует три способа определения коллекции (переменной) с использованием общих подстановочных знаков. Это:
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
Когда использовать 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.
Вы можете просмотреть все ответы выше, чтобы понять, почему .add()
ограничен '<?>'
, '<? extends>'
и отчасти до '<? super>'
.
Но вот вывод всего этого, если вы хотите его запомнить, и не хотите каждый раз изучать ответ:
List<? extends A>
означает, что он примет любой List
из A
и подкласс A
.
Но вы ничего не можете добавить в этот список. Даже объекты типа A
.
List<? super A>
означает, что он примет любой список A
и суперкласс из A
.
Вы можете добавлять объекты типа A
и его подклассы.
Список <? extends X> не позволяет добавлять что-либо, кроме нулевого, в список.
Список <? super X> позволяет добавлять все, что есть - X (X или его подкласс), или null.
Ответные голосовые ответы охватывают детали по многим аспектам. Однако я попытался бы ответить на это по-другому.
Нам нужно рассмотреть 2 вещи,
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>();
'List<? extends X> listvar;'
Вы можете использовать эту функцию, чтобы принять список в аргументах метода и выполнить любые операции с типом X (Примечание: вы можете читать только объекты типа X из списка).
'List<? super Number> listvar;
Вы можете использовать эту функцию, чтобы принять список в аргументах метода и выполнить любые операции с типом Object, поскольку вы можете только читать объекты типа Object из списка. Но да, дополнительная вещь здесь, вы можете добавить объекты типа X в список.
Я хотел бы визуализировать эту разницу. Предположим, что имеем:
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
в изменяемый список независимо от шаблона. |
v
Список <? расширяет X>? может быть либо любым подклассом
X
либо самимX
Список <? супер X>? может быть либо любым суперклассом
X
либо самимX
^
|
Например, порядок наследования принимается как 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 является подклассом. Мы не можем добавить базовый класс в подкласс и почему возникает ошибка. Это то же самое для другого случая.