Разница между <? super T> и <? extends 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>
. - Вы не можете прочитать a,
Double
потому чтоfoo3
это может указывать на aList<Integer>
.
- Вы можете прочитать
Запись - Учитывая вышеуказанные возможные назначения, к какому типу объекта вы могли бы добавить,
List foo3
который был бы допустим для всех вышеуказанных возможныхArrayList
назначений:- Вы не можете добавить
Integer
потомуfoo3
что это может указывать наList<Double>
. - Вы не можете добавить a,
Double
потому чтоfoo3
это может указывать на aList<Integer>
. - Вы не можете добавить a,
Number
потому чтоfoo3
это может указывать на aList<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>
. - Вам не гарантируется, что a
Number
потому чтоfoo3
может указывать на aList<Object>
. - Единственная гарантия заключается в том, что вы получите экземпляр
Object
или подклассObject
(но вы не знаете, какой подкласс).
- Вам не гарантируется,
Запись - Учитывая вышеуказанные возможные назначения, к какому типу объекта вы могли бы добавить,
List foo3
который был бы допустим для всех вышеуказанных возможныхArrayList
назначений:- Вы можете добавить an,
Integer
потому что anInteger
разрешен в любом из приведенных выше списков. - Вы можете добавить экземпляр подкласса
Integer
, потому что экземпляр подклассаInteger
разрешен в любом из приведенных выше списков. - Вы не можете добавить a,
Double
потому чтоfoo3
это может указывать наArrayList<Integer>
. - Вы не можете добавить a,
Number
потому чтоfoo3
это может указывать наArrayList<Integer>
. - Вы не можете добавить an,
Object
потому чтоfoo3
это может указывать наArrayList<Integer>
.
- Вы можете добавить an,
PECS
Remember PECS: "Producer Extends, Consumer Super".
"Producer Extends" - If you need a
List
to produceT
values (you want to readT
s from the list), you need to declare it with? extends T
, e.g.List<? extends Integer>
. But you cannot add to this list."Consumer Super" - If you need a
List
to consumeT
values (you want to writeT
s into the list), you need to declare it with? super T
, e.g.List<? super Integer>
. But there are no guarantees what type of object you may read from this list.If you need to both read from and write to a list, you need to declare it exactly with no wildcards, e.g.
List<Integer>
.
Example
Note this example from the Java Generics FAQ. Note how the source list src
(the producing list) uses extends
, and the destination list dest
(the consuming list) uses 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));
}
}
Also see
How can I add to List<? extends Number> data structures?
Ответ 2
Imagine having this hierarchy
1. Extends
By writing
List<? extends C2> list;
you are saying that list
will be able to reference an object of type (for example) ArrayList
whose generic type is one of the 7 subtypes of C2
(C2
included):
- C2:
new ArrayList<C2>();
, (an object that can store C2 or subtypes) or - D1:
new ArrayList<D1>();
, (an object that can store D1 or subtypes) or - D2:
new ArrayList<D2>();
, (an object that can store D2 or subtypes) or...
and so on. Seven different cases:
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
We have a set of "storable" types for each possible case: 7 (red) sets here graphically represented
As you can see, there is not a safe type that is common to every case:
- you cannot
list.add(new C2(){});
because it could belist = new ArrayList<D1>();
- you cannot
list.add(new D1(){});
because it could belist = new ArrayList<D2>();
and so on.
2. Super
By writing
List<? super C2> list;
you are saying that list
will be able to reference an object of type (for example) ArrayList
whose generic type is one of the 7 supertypes of C2
(C2
included):
- A1:
new ArrayList<A1>();
, (an object that can store A1 or subtypes) or - A2:
new ArrayList<A2>();
, (an object that can store A2 or subtypes) or - A3:
new ArrayList<A3>();
, (an object that can store A3 or subtypes) or...
and so on. Seven different cases:
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
We have a set of "storable" types for each possible case: 7 (red) sets here graphically represented
As you can see, here we have seven safe types that are common to every case: C2
, D1
, D2
, E1
, E2
, E3
, E4
.
- you can
list.add(new C2(){});
because, regardless of the kind of List we're referencing,C2
is allowed - you can
list.add(new D1(){});
because, regardless of the kind of List we're referencing,D1
is allowed
and so on. You probably noticed that these types correspond to the hierarchy starting from type C2
.
Notes
Here the complete hierarchy if you wish to make some tests
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 может быть преобразован по мере их записи, т.Е. Любым суперклассом X...
List<? super X>
Если я получаю список и хочу прочитать X из этого списка, лучше, чтобы это был список X или список вещей, которые могут быть преобразованы в X по мере их чтения, т. Е. Все, что расширяет X
List<? extends X>
Ответ 4
Я хотел бы наглядно представить разницу. Предположим, у нас есть:
class A { }
class B extends A { }
class C extends B { }
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>
- написание и назначение:
|-------------------------|-------------------|-------------------------------------------|
| 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
в изменяемый список независимо от подстановочного знака.