Difference between <? super T> and <? extends T> in Java
我以前用过
1 2 3 | 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 |
阅读-鉴于上述可能的任务,您保证从
- 您可以阅读
Number ,因为可以分配给foo3 的任何列表都包含Number 或Number 的子类。 - 你不能阅读
Integer ,因为foo3 可能指向List 。 - 你不能阅读
Double ,因为foo3 可能指向List 。
写作-考虑到上述可能的任务,您可以在
- 不能添加
Integer ,因为foo3 可能指向List 。 - 不能添加
Double ,因为foo3 可能指向List 。 - 不能添加
Number ,因为foo3 可能指向List 。
你不能在
现在考虑一下
1 2 3 | 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 |
阅读-鉴于上述可能的任务,当您从
- 你不能保证使用
Integer ,因为foo3 可能指向List 或List 。 - 你不能保证一个
Number ,因为foo3 可能指向一个List 。 - 唯一的保证是你会得到一个
Object 或Object 的子类的实例(但你不知道子类是什么)。
写作-考虑到上述可能的任务,您可以在
- 您可以添加一个
Integer ,因为上面的任何列表中都允许使用Integer 。 - 您可以添加
Integer 子类的实例,因为在上述任何列表中都允许使用Integer 子类的实例。 - 不能添加
Double ,因为foo3 可能指向ArrayList 。 - 不能添加
Number ,因为foo3 可能指向ArrayList 。 - 不能添加
Object ,因为foo3 可能指向ArrayList 。
胸肌
记住PECS:"生产者扩展,消费者超级"。
"Producer extends"—如果您需要一个
List 来生成T 值(您想从列表中读取T s),您需要使用? extends T 来声明它,例如List extends Integer> 。但不能添加到此列表。"消费者超级"—如果你需要一个
List 来消费T 值(你想把T 写进列表中),你需要用? super T 来声明它,例如List super Integer> 。但是不能保证您可以从这个列表中读取什么类型的对象。如果您需要同时读取和写入列表,则需要完全声明它,而不使用通配符,例如
List 。
例子
请注意Java泛型FAQ中的这个示例。注意源列表
1 2 3 4 5 6 | 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)); } } |
也看到如何添加到列表<?扩展数字>数据结构?
想象一下有这样的等级制度
1。延伸通过写作
1 | List<? extends C2> list; |
您是说,
等等。七种不同的情况:
1 2 3 4 5 6 7 | 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 。(); - 你不能用
list.add(new D1(){}); ,因为它可能是list = new ArrayList 。();
等等。
2。超级的通过写作
1 | List<? super C2> list; |
您是说,
- a1:
new ArrayList (可以存储a1或子类型的对象)或(); - a2:
new ArrayList (可以存储a2或子类型的对象)或(); - a3:
new ArrayList (可以存储a3或子类型的对象)或…();
等等。七种不同的情况:
1 2 3 4 5 6 7 | 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(红色)组以图形方式表示
如你所见,这里有七种常见的安全类型:
- 你可以使用
list.add(new C2(){}); ,因为不管我们引用的是哪种列表,都允许使用C2 。 - 你可以使用
list.add(new D1(){}); ,因为不管我们引用的是哪种列表,都允许使用D1 。
等等。您可能注意到,这些类型对应于从
这里是完整的层次结构,如果您希望进行一些测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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的列表,或者是x可以向上转换的东西的列表,比如x的任何超类…
1 | List<? super X> |
如果我得到一个列表,我想从列表中读取一个x,那最好是一个x列表,或者一个可以在我读取x时向上投射到x的东西列表,即扩展x的任何东西。
1 | List<? extends X> |
希望这有帮助。
根据伯特F的回答,我想解释一下我的理解。
假设我们有三个班
1 2 3 4 5 | public class Fruit{} public class Melon extends Fruit{} public class WaterMelon extends Melon{} |
这里我们有
1 2 3 | List<? extends Fruit> fruitExtendedList = … //Says that I can be a list of any object as long as this object extends Fruit. |
好了,现在让我们试着从水果扩展列表中获取一些值。
1 2 3 | Fruit fruit = fruitExtendedList.get(position) //This is valid as it can only return Fruit or its subclass. |
让我们再试一次
1 2 3 4 5 | 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. |
情况也是如此
1 | WaterMelon waterMelon = fruitExtendedList.get(position) |
现在让我们尝试在水果扩展列表中设置一些对象。
添加水果对象
1 2 3 4 5 | 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. |
添加瓜对象
1 2 3 4 5 | 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. |
最后让我们尝试添加西瓜对象
1 2 3 4 5 | 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. |
但是等一下,如果有人决定做一种新型的柠檬,我们可以说,为了争论,把盐基柠檬作为
1 | public class SaltyLemon extends Lemon{} |
现在,水果扩展列表可以是水果、瓜类、西瓜或盐类的列表。
所以,我们的声明
1 | fruitExtendedList.add(new WaterMelon()) |
也无效。
基本上,我们可以说我们不能写任何东西到一个结果扩展列表。
这总结了
现在让我们看看
1 2 3 | List<? super Melon> melonSuperList= … //Says that I can be a list of anything as long as its object has super class of Melon. |
现在让我们试着从甜瓜超级清单中获得一些价值。
1 2 3 4 5 | 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 |
同样地,瓜子、西瓜或其他物体也不能被识读。
但是请注意,我们可以读取对象类型实例
1 2 3 4 5 | 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. |
现在,让我们尝试从瓜子超级列表中设置一些值。
添加对象类型对象
1 2 3 4 | 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. |
添加水果类型对象
1 2 3 | melonSuperList.add(new Fruit()) //This is also not valid as melonSuperList can be list of Melon |
添加瓜类对象
1 2 3 4 | 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. |
添加西瓜类型对象
1 2 3 | melonSuperList.add(new WaterMelon()) //This is also valid because of same reason as adding Melon |
综上所述,我们可以在MeloSuperList和只读对象类型对象中添加Melon或其子类。
super是下界,extends是上界。
根据http://download.oracle.com/javase/tutorial/extra/generics/morefun.html:
The solution is to use a form of
bounded wildcard we haven't seen yet:
wildcards with a lower bound. The
syntax ? super T denotes an unknown
type that is a supertype of T (or T
itself; remember that the supertype
relation is reflexive). It is the dual
of the bounded wildcards we've been
using, where we use ? extends T to
denote an unknown type that is a
subtype of T.
我想想象一下两者的区别。假设我们有:
1 2 3 | class A { } class B extends A { } class C extends B { } |
1 2 3 4 5 6 7 8 9 | |-------------------------|-------------------|---------------------------------| | wildcard | get | assign | |-------------------------|-------------------|---------------------------------| | List<? extends C> | A B C | List<C> | |-------------------------|-------------------|---------------------------------| | List<? extends B> | A B | List List<C> | |-------------------------|-------------------|---------------------------------| | List<? extends A> | A | List<A> List List<C> | |-------------------------|-------------------|---------------------------------| |
1 2 3 4 5 6 7 8 9 | |-------------------------|-------------------|-------------------------------------------| | wildcard | add | assign | |-------------------------|-------------------|-------------------------------------------| | List<? super C> | C | List<Object> List<A> List List<C> | |-------------------------|-------------------|-------------------------------------------| | List<? super B> | B C | List<Object> List<A> List | |-------------------------|-------------------|-------------------------------------------| | List<? super A> | A B C | List<Object> List<A> | |-------------------------|-------------------|-------------------------------------------| |
在所有情况下:
- 无论通配符如何,都可以从列表中获取
Object 。 - 无论通配符如何,您都可以将
null 添加到可变列表中。
向列表中添加项目:
列表<?扩展X>不允许在列表中添加任何内容,除了
null 。列表<?super x>允许添加X(X或其子类型)或空的任何内容。
从列表中获取项目:
- 当你从列表中得到一个项目时<?扩展X>,您可以将其分配给X类型的变量或X的任何父类型,包括对象。
- 当你从列表中得到一个项目时<?super x>,只能将它赋给
Object 类型的变量。
一些例子:
1 2 3 4 5 6 7 8 9 10 | 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 |
1 2 3 4 5 6 7 8 9 10 | 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 |
使用扩展只能从集合中获取。你不能放进去。另外,尽管super允许get和put,get期间的返回类型是?超级T
你可以通过以上所有的答案来理解为什么
但是,如果你想记住它,并且不想每次都去探索答案的话,下面是所有这些的结论:
这里最令人困惑的是,无论我们指定了什么类型的限制,赋值只能以一种方式工作:
1 | baseClassInstance = derivedClassInstance; |
你可能认为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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> } } |
edit增加了一条关于消费者扩展和生产者超级的评论——它们没有意义,因为它们相应地没有指定任何内容,只是
投票通过的答案涵盖了许多方面的细节。但是,我会尝试用不同的方式来回答这个问题。
我们需要考虑两件事,
1。为列表变量赋值这里,x的任何列表或x的子类列表都可以分配给listvar。
listvar = new ArrayList
listvar = new ArrayList
这里,x的任何列表或x的超类列表都可以分配给listvar。
listvar = new ArrayList
listvar = new ArrayList
1 | `List<? extends X> listvar;` |
可以使用此功能接受方法参数中的列表,并对类型X执行任何操作(注意:只能从列表中读取类型X的对象)。
1 | `List<? super Number> listvar; |
您可以使用此功能接受方法参数中的列表,并对类型对象执行任何操作,因为您只能从列表中读取类型对象的对象。但是,这里还有一点,您可以将X类型的对象添加到列表中。
何时使用extends和super
通配符在方法参数中最有用。它们允许在方法接口中具有必要的灵活性。
人们经常混淆什么时候使用扩展,什么时候使用超边界。经验法则是即得即得原则。如果您从参数化容器中获得了一些内容,请使用扩展。
1 2 3 4 5 6 | int totalFuel(List<? extends Vehicle> list) { int total = 0; for(Vehicle v : list) { total += v.getFuel(); } return total;} |
方法totalFuel从列表中获取车辆,询问他们有多少燃油,并计算总数。如果将对象放入参数化容器中,请使用super。
1 2 3 4 5 6 | int totalValue(Valuer<? super Vehicle> valuer) { int total = 0; for(Vehicle v : vehicles) { total += valuer.evaluate(v); } return total;} |
方法totalValue将车辆放入值器。知道扩展界比超界更常见是很有用的。
通用通配符针对两个主要需求:
从一般集合读取插入到泛型集合中有三种方法可以使用通用通配符定义集合(变量)。这些是:
1 2 3 | List<?> listUknown = new ArrayList<A>(); List<? extends A> listUknown = new ArrayList<A>(); List<? super A> listUknown = new ArrayList<A>(); |
阅读更多:http://tutorials.jenkov.com/java-generics/widcards.html
例子,继承顺序假定为O>S>T>U>V。
使用扩展关键字,
对的:
1 2 3 |
不正确的:
超级关键词:
对的:
1 2 3 |
不正确的:
添加对象:list对象=new list();
1 |
但是为什么会出错呢?让我们看看初始化列表对象的可能性
1 2 3 |
如果我们使用object.add(new t());那么只有在
1 |
但还有另外两种可能性
list对象=new list();list对象=new list();如果我们试图将(new t())添加到上述两种可能性中,它将给出一个错误,因为t是u和v的高级类。我们尝试将t对象[即(new t())]添加到类型U和V的列表中。高级对象(基类)不能传递给低级对象(子类)。
由于额外的两种可能性,即使使用正确的属性,Java也会给您带来错误,因为Java不知道您所指的对象。因此,不能将对象添加到ListObjs=新ListLe()中,因为有可能是无效的。
添加对象:list对象=new list();
1 2 3 4 5 6 |
但是,为什么上面两个错误会发生呢?我们可以使用object.add(new t());只有在下面的可能性中,
1 2 3 |
如果我们试图在list对象=new list();和list对象=new list();然后就会出错这是因为我们不能将t对象[这是新的t()]添加到list object=new list();因为它是U类型的对象。我们不能将t对象[这是新的t()]添加到u对象,因为t是基类,u是子类。我们不能向子类添加基类,这就是发生错误的原因。另一种情况也是如此。