<! - ?之间的区别超级T - >和<! - ?在Java中扩展T - >

时间:2010-12-03 06:57:06

标签: java generics collections

List<? super T>List<? extends T>之间的区别是什么?

我过去常常使用List<? extends T>,但它不允许我向list.add(e)添加元素,而List<? super T>则会这样做。

14 个答案:

答案 0 :(得分:1295)

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
  1. 阅读 - 鉴于上述可能的分配,您可以保证从List foo3读取哪种类型的对象:

    • 您可以阅读 Number ,因为可以分配给foo3的任何列表都包含NumberNumber的子类
    • 您无法阅读Integer,因为foo3可能指向List<Double>
    • 您无法阅读Double,因为foo3可能指向List<Integer>
  2. 写作 - 鉴于上述可能的分配,您可以向List foo3添加哪种类型的对象,这对于所有以上可能是合法的ArrayList作业:

    • 您无法添加Integer,因为foo3可能指向List<Double>
    • 您无法添加Double,因为foo3可能指向List<Integer>
    • 您无法添加Number,因为foo3可能指向List<Integer>
  3. 您无法向List<? extends T>添加任何对象,因为您无法保证它实际指向的是哪种List,因此您无法保证该对象是允许的在那List。唯一的“保证”是您只能从中读取,并且您将获得T的{​​{1}}或子类。

    T

    现在考虑super

    List <? super T>的通配符声明表示其中任何一项都是合法分配:

    List<? super Integer> foo3
    1. 阅读 - 鉴于上述可能的分配,当您从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可能指向Integerfoo3,因此无法保证List<Number>
      • 由于List<Object>可能指向Number,因此无法保证foo3
      • 唯一保证您将获得 List<Object> 的实例或Object的子类(但您不知道是什么子类)。
    2. 写作 - 鉴于上述可能的分配,您可以向Object添加哪种类型的对象,这对于所有以上可能是合法的List foo3作业:

      • 您可以添加ArrayList,因为上述任何一个列表都允许Integer
      • 您可以添加Integer子类的实例,因为在上述任何列表中都允许Integer的子类实例。
      • 您无法添加Integer,因为Double可能指向foo3
      • 您无法添加ArrayList<Integer>,因为Number可能指向foo3
      • 您无法添加ArrayList<Integer>,因为Object可能指向foo3
    3. PECS

      记住 PECS “制作人扩展,消费者超级”

      • “制作者扩展” - 如果您需要ArrayList<Integer>来生成List值(您希望从列表中读取T) ,你需要用T声明它,例如? extends T。但您无法添加到此列表中。

      • “消费者超级” - 如果您需要List<? extends Integer>消费List值(您希望将T写入列表) ,你需要用T声明它,例如? super T。但是无法保证您可以从此列表中读取什么类型的对象。

      • 如果您需要同时读取和写入列表,则需要在没有通配符的情况下完全声明它,例如: List<? super Integer>

      实施例

      注意this example from the Java Generics FAQ。请注意源列表List<Integer>(生成列表)如何使用src,目标列表extends(使用列表)使用dest

      super

      另见 How can I add to List<? extends Number> data structures?

答案 1 :(得分:203)

想象一下这个层次结构

enter image description here

1。延伸

写作

    List<? extends C2> list;

您说list将能够引用类型的对象(例如)ArrayList,其泛型类型是{{1>的7个子类型之一}(包括C2):

  1. C2: C2,(可存储C2或子类型的对象)或
  2. D1: new ArrayList<C2>();,(可存储D1或子类型的对象)或
  3. D2: new ArrayList<D1>();,(可存储D2或子类型的对象)或......
  4. 等等。七种不同的案例:

    new ArrayList<D2>();

    我们为每种可能的案例都有一组“可存储”类型:7(红色)集合在这里用图形表示

    enter image description here

    如您所见,每种情况都没有安全类型

    • 您不能 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 ,因为它可能是list.add(new C2(){});
    • 您不能list = new ArrayList<D1>();,因为它可能是list.add(new D1(){});

    等等。

    2。超

    写作

    list = new ArrayList<D2>();

    你说 List<? super C2> list; 将能够引用类型的对象(例如)list,其泛型类型是{{1>的7个超类型之一}(包括ArrayList):

    • A1: C2,(可存储A1或子类型的对象)或
    • A2: C2,(可以存储A2或子类型的对象)或
    • A3: new ArrayList<A1>();,(可以存储A3或子类型的对象)或......

    等等。七种不同的案例:

    new ArrayList<A2>();

    我们为每种可能的案例都有一组“可存储”类型:7(红色)集合在这里用图形表示

    enter image description here

    正如您所看到的,此处我们有七种安全类型,每种情况都相同:new ArrayList<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 C2,{{1} },D1D2E1

    • 您可E2因为,无论我们引用哪种列表,都允许E3
    • 您可E4因为,无论我们引用哪种列表,都允许list.add(new C2(){});

    等等。您可能已经注意到这些类型对应于从类型C2开始的层次结构。

    注释

    如果你想进行一些测试,这里是完整的层次结构

    list.add(new D1(){});

答案 2 :(得分:75)

我喜欢@Bert F的答案,但这是我大脑看到它的方式。

我手里拿着一个X.如果我想我的X写入List,那么List必须是X的列表或者我可以将其写入的内容列表,因为我将其写入,即任何 X的超类 ...

List<? extends X>

如果我得到一个List并且我希望读取该列表中的X,那么当我读出它时,最好是X的列表或可以向上转换为X的事物列表,即任何扩展 X

{{1}}

希望这会有所帮助。

答案 3 :(得分:17)

基于Bert F's answer我想解释一下我的理解。

假设我们有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.

但是等等 如果某人决定制作一种新型的柠檬,请为SalquLemon说明

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

同样不能读取Melon,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.

添加水果类型对象

melonSuperList.add(new Fruit())

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

添加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

总结一下,我们可以在melonSuperList中添加Melon或其子类,并且只读取Object类型对象。

答案 4 :(得分:14)

super是下限,extends是上限。

根据http://download.oracle.com/javase/tutorial/extra/generics/morefun.html

  

解决方案是使用一种形式   有限的通配符我们还没有看到:   具有下限的通配符。该   句法 ? super T表示未知   类型是T(或T)的超类型   本身;记住那个超类型   关系是反身的)。这是双重的   我们去过的有界通配符   使用,我们在哪里使用?将T扩展为   表示一个未知的类型   T的子类型。

答案 5 :(得分:9)

我想看一下区别。假设我们有:

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到可变列表。

答案 6 :(得分:3)

使用 extends ,您只能从该集合中获取。你不能投入其中。另外,虽然 super 允许get和put,但get期间的返回类型是?超级T

答案 7 :(得分:3)

将项目添加到列表中:

  • <强>列表&LT; ? extends X&gt; 不允许在列表中添加除null之外的任何内容。

  • <强>列表&LT; ? super X&gt; 允许添加任何X(X或其子类型)或null。

从列表中获取项目:

  • 当您从列表&lt;中获取项目时?扩展X&gt; ,您可以将其分配给X类型的变量或X的任何超类型,包括Object。
  • 当您从列表&lt;中获取项目时?超级X&gt; ,您只能将其分配给Object类型的变量。

一些例子:

    List<? extends Number> list1 = new ArrayList<Integer>();
    list1.add(null);  //OK
    Number n = list1.get(0);  //OK
    Serializable s = list1.get(0);  //OK
    Object o = list1.get(0);  //OK

    list1.add(2.3);  //ERROR
    list1.add(5);  //ERROR
    list1.add(new Object());  //ERROR
    Integer i = list1.get(0);  //ERROR
    List<? super Number> list2 = new ArrayList<Number>();
    list2.add(null);  //OK
    list2.add(2.3);  //OK
    list2.add(5);  //OK
    Object o = list2.get(0);  //OK

    list2.add(new Object());  //ERROR
    Number n = list2.get(0);  //ERROR
    Serializable s = list2.get(0);  //ERROR
    Integer i = list2.get(0);  //ERROR

答案 8 :(得分:2)

这里最令人困惑的是,无论我们指定什么类型限制,分配只能以一种方式运行:

baseClassInstance = derivedClassInstance;

您可能会认为Integer extends NumberInteger<? extends Number>作为<? extends Number> cannot be converted to 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> } } (也就是人类用语, 扩展数字的任何内容都可以转换为Integer ):

hs.set(i);

Integer没问题,因为 Number可以转换为Integer 的任何超类(而不是因为NumberObject的超类1}},这不是真的。)

EDIT添加了关于Consumer Extends和Producer Super的评论 - 它们没有意义,因为它们相应地指定了 nothing {{1}} 。建议您记住PECS,因为CEPS永远不会有用。

答案 9 :(得分:2)

您可以查看上述所有答案,了解为什么.add()仅限于'<?>''<? extends>',部分限于'<? super>'

但如果您想记住它,并且不想每次都去探索答案,那么这就是结论:

List<? extends A>表示这将接受List的任何AA的子类。 但是您无法在此列表中添加任何内容。甚至不是A类型的对象。

List<? super A>表示这将接受A的任何列表和A的超类。 您可以添加A类型的对象及其子类。

答案 10 :(得分:1)

通用通配符针对两个主要需求:

从通用集合中读取 插入通用集合 使用通用通配符定义集合(变量)有三种方法。这些是:

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

List<?>表示键入未知类型的列表。这可以是List<A>List<B>List<String>等。

List<? extends A>表示作为class Asubclasses of A(例如B和C)实例的对象列表。 List<? super A>表示该列表已键入A classsuperclass of A

了解详情:http://tutorials.jenkov.com/java-generics/wildcards.html

答案 11 :(得分:1)

何时使用extends和super

通配符在方法参数中最有用。它们允许方法接口具有必要的灵活性。

人们常常会在使用扩展时和使用超边界时感到困惑。经验法则是获取原则。如果从参数化容器中获取内容,请使用extends。

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

方法totalFuel从列表中获取车辆,询问他们有多少燃料,并计算总数。 如果将对象放入参数化容器中,请使用super。

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

方法totalValue将车辆纳入估值师。 知道扩展边界比超级更常见是有用的。

答案 12 :(得分:1)

最高投票答案涵盖了许多方面的细节。但是,我会尝试以不同的方式回答这个问题。

我们需要考虑两件事,

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;

您可以使用此功能接受方法参数中的列表,并对类型对象执行任何操作,因为您可以仅从列表中读取对象类型的对象。 但是,是的,此处还有一件事是,您可以将X类型的对象添加到列表中。

答案 13 :(得分:0)

例如, 继承顺序假定为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.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(new T());那么只有

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

但是还有另外两种可能性

列表对象=新的List(); 列表对象=新的List(); 如果我们尝试将(new T())添加到上述两种可能性,则将产生错误,因为T是U和V的上一类。我们尝试向类型U和V的List添加一个T对象[[new T())]。较高级别的对象(基类)不能传递给较低级别​​的对象(子类)。

由于这两种可能性,即使您使用正确的可能性,Java也会给您带来错误,因为Java不知道您指的是什么对象。因此,您不能将对象添加到List Object = new List();因为存在无效的可能性。

添加对象: 列表对象=新的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(new T());仅在以下可能性下,

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

如果我们尝试在以下对象中使用Object.add(new T())   列表对象=新的List();             和   列表对象=新的List(); 然后会给出错误 这是因为 我们不能将T对象[这是new T()]添加到List Object = new List();中。因为它是类型U的对象。我们不能向U Object添加T对象[这是新的T()],因为T是基类,而U是子类。我们不能将基类添加到子类中,这就是发生错误的原因。对于另一种情况也是如此。