每当您将 both put 值分配到同一结构的 and get 值时,您就 should not use a wildcard .
public static double sumCount(Collection<Number> nums, int n) {
count(nums, n);
return sum(nums);
}
27
public class Test {
public class A {}
public class B extends A {}
public class C extends B {}
public void testCoVariance(List<? extends B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b); // does not compile
myBlist.add(c); // does not compile
A a = myBlist.get(0);
}
public void testContraVariance(List<? super B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b);
myBlist.add(c);
A a = myBlist.get(0); // does not compile
}
}
17
PECS(制片人 extends 和消费者 super )
助记符→获取和放置原则 .
该原则指出:
当您只从结构中获取值时,请使用扩展通配符 .
仅在将值放入结构时使用超级通配符 .
并且当你得到和放置时不要使用通配符 .
在Java中,参数和泛型类型参数不支持如下继承 .
class Super {
void testCoVariance(Object parameter){} // method Consumes the Object
Object testContraVariance(){ return null;} //method Produces the Object
}
class Sub extends Super {
@Override
void testCoVariance(String parameter){} //doesn't support eventhough String is subtype of Object
@Override
String testContraVariance(){ return null;} //compiles successfully i.e. return type is don't care
}
Liskov substitution principle:Arrays 是协变的(不安全的)但是泛型不是那么(不安全) . 即替换原则不适用于参数化类型,这意味着它是非法的 . 协变只是意味着 X 是 Y 的子类型,那么 X[] 也将是 Y[] 的子类型 .
Object name= new String("prem"); //works
List<Number> numbers = new ArrayList<Integer>();//gets compile time error
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
myNumber[0] = 3.14; //attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)
List<String> list=new ArrayList<>();
list.add("prem");
List<Object> listObject=list; //Type mismatch: cannot convert from List<String> to List<Object> at Compiletime
协方差: ? extends T ( T 的子类型的所有类型的族) - 带有 upper bound 的通配符 . T 是继承层次结构中的 upper -most类 . 当结构中只有 Get 值时,请使用 extends 通配符 .
Contra-variance: ? super T ( T 的超类型的所有类型的族) - 带 lower bound 的通配符 . T 是继承层次结构中的 lower -most类 . 当只有 Put 值进入结构时,请使用 super 通配符 .
注意:通配符 ? 表示 zero or one time ,表示未知类型 . 通配符可以用作参数的类型,从不用作泛型的类型参数方法调用,一个通用的类实例创建 . (即当使用通配符时,引用未在程序中的其他地方使用,就像我们使用 T )
class Shape { void draw() {}}
class Circle extends Shape {void draw() {}}
class Square extends Shape {void draw() {}}
class Rectangle extends Shape {void draw() {}}
public class TestContraVariance {
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Shape()); // Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(new Circle()); // Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(new Square()); // Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(new Rectangle()); // Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
Shape shape= list.get(0);//compiles so list act as produces only
/*You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can get an object and know that it will be an Shape
*/
}
/*
* Example for a lower bound wildcard (Put values i.e Consumer`super`)
* */
public void testContraVariance(List<? super Shape> list) {
list.add(new Shape());//compiles i.e. inheritance is supporting
list.add(new Circle());//compiles i.e. inheritance is supporting
list.add(new Square());//compiles i.e. inheritance is supporting
list.add(new Rectangle());//compiles i.e. inheritance is supporting
Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
Object object= list.get(0); // gets an object, but we don't know what kind of Object it is.
/*You can add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
请注意,通常您应该只使用 ? extends T 和 ? super T 作为某些方法的参数 . 方法应该只使用 T 作为泛型返回类型的类型参数 .
136
简而言之,容易记住PECS
如果需要从集合中检索 T 类型的对象,请使用 <? extends T> 通配符 .
如果需要将 T 类型的对象放入集合中,请使用 <? super T> 通配符 .
如果你需要满足这两个要求,那就不要使用任何通配符 . 尽可能简单 .
20
(添加一个答案,因为从来没有足够的Generics通配符示例)
// Source
List<Integer> intList = Arrays.asList(1,2,3);
List<Double> doubleList = Arrays.asList(2.78,3.14);
List<Number> numList = Arrays.asList(1,2,2.78,3.14,5);
// Destination
List<Integer> intList2 = new ArrayList<>();
List<Double> doublesList2 = new ArrayList<>();
List<Number> numList2 = new ArrayList<>();
// Works
copyElements1(intList,intList2); // from int to int
copyElements1(doubleList,doublesList2); // from double to double
static <T> void copyElements1(Collection<T> src, Collection<T> dest) {
for(T n : src){
dest.add(n);
}
}
// Let's try to copy intList to its supertype
copyElements1(intList,numList2); // error, method signature just says "T"
// and here the compiler is given
// two types: Integer and Number,
// so which one shall it be?
// PECS to the rescue!
copyElements2(intList,numList2); // possible
// copy Integer (? extends T) to its supertype (Number is super of Integer)
private static <T> void copyElements2(Collection<? extends T> src,
Collection<? super T> dest) {
for(T n : src){
dest.add(n);
}
}
0
我们假设这个层次结构:
class Creature{}// X
class Animal extends Creature{}// Y
class Fish extends Animal{}// Z
class Shark extends Fish{}// A
class HammerSkark extends Shark{}// B
class DeadHammerShark extends HammerSkark{}// C
Let's clarify PE - Producer Extends:
List<? extends Shark> sharks = new ArrayList<>();
为什么你不能在这个列表中添加扩展“Shark”的对象?喜欢:
sharks.add(new HammerShark());//will result in compilation error
由于您的列表可以是A,B或C at runtime 类型,因此您无法在其中添加任何类型为A,B或C的对象,因为您最终可能会得到java中不允许的组合 . 实际上,编译器确实可以在编译时看到你添加一个B:
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
在运行时,列表的类型可以是A:X,Y,Z,......之上的任何类型 . 编译器可以编译你的赋值语句(看起来是正确的)但是, at runtime s(Animal)的类型可以在层次结构中更低比列表的声明类型(可能是Creature,或更高) . 这是不允许的 .
To sum up
We use <? super T> to add objects of types equal or below T in list. 我们无法从中读取 . We use <? extends T> to read objects of types equal or below T from list. 我们无法添加元素 .
12 回答
记住这一点:
tl;dr: "PECS"来自集合的观点 . 如果您只是从通用集合中提取项目,那么它是 生产环境 者,您应该使用
extends
;如果你只是填充物品,它是一个消费者,你应该使用super
. 如果同时使用相同的集合,则不应使用extends
或super
.假设您有一个方法,它将事物的集合作为其参数,但您希望它比仅接受
Collection<Thing>
更灵活 .Case 1: You want to go through the collection and do things with each item.
然后列表是 producer ,所以你应该使用
Collection<? extends Thing>
.原因是
Collection<? extends Thing>
可以保存Thing
的任何子类型,因此当您执行操作时,每个元素都将表现为Thing
. (您实际上无法向Collection<? extends Thing>
添加任何内容,因为您无法在运行时知道集合中包含的Thing
的哪个特定子类型 . )Case 2: You want to add things to the collection.
然后列表是 consumer ,所以你应该使用
Collection<? super Thing>
.这里的推理是,与
Collection<? extends Thing>
不同,无论实际参数化类型是什么,Collection<? super Thing>
总是可以保持Thing
. 在这里你不关心列表中已有的内容,只要它允许添加Thing
;这是? super Thing
的保证 .计算机科学背后的原理以其命名
协方差 - ?扩展MyClass,
逆变 - ?超级MyClass和
不变性/非差异 - MyClass
下面的图片应该解释这个概念 .
图片礼貌:Andrey Tyukin
PECS(“ Producer extends and Consumer super ”的缩写)可以解释为: Get and Put Principle
获取和放置原则(来自Java泛型和集合)
它指出,
当你只有一个结构中的 get 值时,
当你只有 put 值进入一个结构时
_999_和 don’t use a wildcard 当你 both get and put .
让我们通过例子来理解它:
1. For Extends Wildcard(get values i.e Producer extends)
这是一个方法,它采用一组数字,将每个数字转换为
double
,并将它们相加我们称之为方法:
自 sum() method uses extends 以来,以下所有电话都是合法的 . 如果未使用扩展,前两个调用将不合法 .
EXCEPTION :您 cannot put anything 进入使用 extends 通配符声明的类型 - 除了值
null
,它属于每个引用类型:2. For Super Wildcard(put values i.e Consumer super)
这是一个方法,它采用数字和
int n
的集合,并将第一个n
整数从零开始放入集合中:我们称之为方法:
由于 count() method uses super ,以下所有呼叫都是合法的:如果未使用超级呼叫,则最后两次呼叫将不合法 .
EXCEPTION :你 cannot get anything 来自用 super 通配符声明的类型 - 除了
Object
类型的值,它是每个引用类型的超类型:3. When both Get and Put, don't Use wildcard
每当您将 both put 值分配到同一结构的 and get 值时,您就 should not use a wildcard .
PECS(制片人
extends
和消费者super
)助记符→获取和放置原则 .
该原则指出:
当您只从结构中获取值时,请使用扩展通配符 .
仅在将值放入结构时使用超级通配符 .
并且当你得到和放置时不要使用通配符 .
在Java中,参数和泛型类型参数不支持如下继承 .
Liskov substitution principle: Arrays 是协变的(不安全的)但是泛型不是那么(不安全) . 即替换原则不适用于参数化类型,这意味着它是非法的 .
协变只是意味着
X
是Y
的子类型,那么X[]
也将是Y[]
的子类型 .more examples
bounded (即前往某处) wildcard :有3种不同的通配符:
方差/非方差:
?
或? extends Object
- Unbounded 通配符 . 它代表所有类型的家庭 . 当你得到和放置时使用 .协方差:
? extends T
(T
的子类型的所有类型的族) - 带有 upper bound 的通配符 .T
是继承层次结构中的 upper -most类 . 当结构中只有 Get 值时,请使用extends
通配符 .Contra-variance:
? super T
(T
的超类型的所有类型的族) - 带 lower bound 的通配符 .T
是继承层次结构中的 lower -most类 . 当只有 Put 值进入结构时,请使用super
通配符 .注意:通配符
?
表示 zero or one time ,表示未知类型 . 通配符可以用作参数的类型,从不用作泛型的类型参数方法调用,一个通用的类实例创建 . (即当使用通配符时,引用未在程序中的其他地方使用,就像我们使用T
)generics和examples
正如我在另一个问题中解释的那样,PECS是由Josh Bloch创建的助记符设备,用于帮助记住 P roducer
extends
, C onsumersuper
.请注意,通常您应该只使用
? extends T
和? super T
作为某些方法的参数 . 方法应该只使用T
作为泛型返回类型的类型参数 .简而言之,容易记住PECS
如果需要从集合中检索
T
类型的对象,请使用<? extends T>
通配符 .如果需要将
T
类型的对象放入集合中,请使用<? super T>
通配符 .如果你需要满足这两个要求,那就不要使用任何通配符 . 尽可能简单 .
(添加一个答案,因为从来没有足够的Generics通配符示例)
我们假设这个层次结构:
Let's clarify PE - Producer Extends:
为什么你不能在这个列表中添加扩展“Shark”的对象?喜欢:
由于您的列表可以是A,B或C at runtime 类型,因此您无法在其中添加任何类型为A,B或C的对象,因为您最终可能会得到java中不允许的组合 .
实际上,编译器确实可以在编译时看到你添加一个B:
...但是无法判断在运行时,您的B将是列表类型的子类型还是超类型 . 在运行时,列表类型可以是A,B,C类型中的任何一种 . 因此,您最终无法在DeadHammerShark列表中添加HammerSkark(超类型) .
*你会说:"OK, but why can't I add HammerSkark in it since it is the smallest type?" . 答:这是最小的 you 知道 . 购买HammerSkark也可以被其他人扩展,你最终会遇到同样的情况 .
Let's clarify CS - Consumer Super:
在同一层次结构中,我们可以尝试这样:
你 can 添加到此列表的内容和原因是什么?
您可以添加上述类型的对象,因为鲨鱼(A,B,C)下方的任何内容都将始终是鲨鱼(X,Y,Z)上方的任何子类型 . 容易明白 .
你 cannot 在Shark上面添加类型,因为 at runtime 添加对象的类型可以在层次结构中高于列表的声明类型(X,Y,Z) . 这是不允许的 .
但为什么你不能从这个列表中读取? (我的意思是你可以从中获取一个元素,但你不能将它分配给Object o以外的任何东西):
在运行时,列表的类型可以是A:X,Y,Z,......之上的任何类型 . 编译器可以编译你的赋值语句(看起来是正确的)但是, at runtime s(Animal)的类型可以在层次结构中更低比列表的声明类型(可能是Creature,或更高) . 这是不允许的 .
To sum up
We use <? super T> to add objects of types equal or below T in list. 我们无法从中读取 .
We use <? extends T> to read objects of types equal or below T from list. 我们无法添加元素 .
下限通配符(?超类型) .
无界通配符(?) .
出于本讨论的目的,将变量视为提供两个函数之一是有帮助的: - 在变量中
“in”变量向代码提供数据 .
想象一下带有两个参数的复制方法:
copy(src,dest)
src参数提供要复制的数据,因此它是“in”参数 .
“out”变量保存数据以供其他地方使用 . 在复制示例中,
copy(src,dest)
dest参数接受数据,因此它是“out”参数 .
使用extends关键字定义带有上限通配符的“in”变量 .
使用super关键字定义带有下限通配符的“out”变量 .
在可以使用Object类中定义的方法访问“in”变量的情况下,使用无界通配符 .
在代码需要作为“in”和“out”变量访问变量的情况下,不要使用通配符 .
类NaturalNumber
{
私人网络;
public NaturalNumber(int i)
}
类EvenNumber扩展了NaturalNumber
{
public EvenNumber(int i)
{
超级(ⅰ);
}
}
请考虑以下代码:
List <EvenNumber> le = new ArrayList <>();
名单<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35)); //编译时错误
您可以添加null .
你可以调用清除 .
您可以获取迭代器并调用remove .
您可以捕获通配符并写入从列表中读取的元素 .
使用现实生活中的例子(有一些简化):
想象一下货车与列车类似的货运列车 .
如果货物 the same or smaller size 比货车=
<? super FreightCarSize>
,您可以 put 货运车中的货物如果您的仓库中有 enough place (超过货物的大小),您可以 unload 来自货车的货物=
<? extends DepotSize>