pea*_*kit 680 java generics super pecs bounded-wildcard
我在阅读泛型时遇到了PECS(制片extends
人和消费者的super
简称).
能否给我一个人解释如何使用佩奇之间解决困惑extends
和super
?
Mic*_*ers 792
tl; dr: "PECS"来自集合的观点.如果您只是从通用集合中提取项目,那么它就是生产者,您应该使用extends
; 如果你只是填充物品,它是消费者,你应该使用super
.如果您同时使用相同的集合,则不应使用extends
或super
.
假设您有一个方法,它将事物的集合作为参数,但您希望它比仅接受一个更灵活Collection<Thing>
.
案例1:您希望浏览集合并对每个项目执行操作.
然后列表是生产者,所以你应该使用Collection<? extends Thing>
.
原因是a Collection<? extends Thing>
可以保存任何子类型Thing
,因此每个元素Thing
在执行操作时都会表现为.(您实际上无法向a添加任何内容Collection<? extends Thing>
,因为您无法在运行时知道该集合的哪个特定子类型Thing
.)
案例2:您想要将东西添加到集合中.
然后列表是消费者,所以你应该使用Collection<? super Thing>
.
这里的推理是不同的Collection<? extends Thing>
,无论实际的参数化类型是什么,Collection<? super Thing>
都可以随时保持Thing
.在这里你不关心列表中已有的内容,只要它允许Thing
添加; 这就是? super Thing
保证.
ano*_*ias 524
计算机科学背后的原理被称为
? extends MyClass
,? super MyClass
和MyClass
下面的图片应该解释这个概念.
图片提供:Andrey Tyukin
Pre*_*raj 40
PECS(制片extends
人和消费者super
)
助记符→获取和放置原则.
该原则指出:
在Java中,参数和泛型类型参数不支持如下继承.
class Super {
Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}
class Sub extends Super {
@Override
String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object)
@Override
void testContraVariance(String parameter){} //doesn't support even though String is subtype of Object
}
Run Code Online (Sandbox Code Playgroud)
Liskov替换原则: 数组是协变的(不安全的)但是泛型不是那么(不安全).即替换原则不适用于参数化类型,这意味着它是非法的.
协变只是意味着如果?
是子类型? extends Object
那么? extends T
也将是子类型T
.
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
Run Code Online (Sandbox Code Playgroud)
有界(即朝向某处)通配符:有3种不同的通配符:
T
或extends
- 无界通配符.它代表所有类型的家庭.当你得到和放置时使用.? super T
所有类型的子类型 T
) - 具有上限的通配符.T
是继承层次结构中最上面的类.super
仅在从结构中获取值时使用通配符.?
所有类型的超类型族 T
) - 具有下限的通配符.extends
是较低的继承层次结构中最末端的类.super
仅在将值放入结构时使用通配符.注意:通配符?
表示零次或一次,表示未知类型.通配符可以用作参数的类型,从不用作泛型方法调用的类型参数,通用类实例创建.(即使用通配符时,引用未在我们使用的程序中的其他地方使用? extends Object
)
class Shape { void draw() {}}
class Circle extends Shape {void draw() {}}
class Square extends Shape {void draw() {}}
class Rectangle extends Shape {void draw() {}}
public class Test {
/*
* 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<? super Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
Run Code Online (Sandbox Code Playgroud)
Gab*_*ica 29
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
}
}
Run Code Online (Sandbox Code Playgroud)
Col*_*inD 23
正如我在解释我的回答另一个问题时,佩奇是乔希布洛赫为了帮助记住一个记忆装置P roducer extends
,Ç onsumer super
.
这意味着,当一个参数化的类型被传递给的方法将产生的实例
T
(它们将从它以某种方式被检索),? extends T
应该被使用,因为子类的任意实例T
也是T
.当一个参数化类型被传递给方法会消耗的情况下,
T
(他们将被传递给它做一些事情),? super T
应该使用,因为实例T
可以合法地传递给一个接受的某些超类型的任何方法T
.例如,AComparator<Number>
可用于aCollection<Integer>
.? extends T
不行,因为一个Comparator<Integer>
不能操作Collection<Number>
.
请注意,通常您应该只使用? extends T
和使用? super T
某些方法的参数.方法应该只T
用作泛型返回类型的类型参数.
Pra*_*hal 21
简而言之,要记住PECS有三个简单的规则:
<? extends T>
如果需要T
从集合中检索类型的对象,请使用通配符.<? super T>
如果需要将类型的对象放入集合中,请使用通配符T
.kaa*_*aan 10
这是我想到的最清晰,最简单的方法扩展与超级:
extends
是为了阅读
super
是为了写作
我发现“PECS”是一种非显而易见的方式来思考谁是“生产者”和谁是“消费者”。“PECS”是从的角度定义数据集合本身-集“消耗”,如果对象被写入到它(它是消费来自呼叫码对象),并且它“生产”,如果正在读的对象从它(它正在为某些调用代码生成对象)。这与其他所有内容的命名方式相反。标准 Java API 从调用代码的角度命名,而不是从集合本身的角度命名。例如,java.util.List的以集合为中心的视图应该有一个名为“receive()”而不是“add()”的方法——毕竟,元素,但列表本身接收元素。
我认为从与集合交互的代码的角度思考事情更直观、自然和一致——代码是“读取”还是“写入”集合?之后,写入集合的任何代码都将是“生产者”,而从集合中读取的任何代码都将是“消费者”。
(添加答案因为从来没有足够的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);
}
}
Run Code Online (Sandbox Code Playgroud)
我们假设这个层次结构:
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
Run Code Online (Sandbox Code Playgroud)
让我们澄清一下PE - Producer扩展:
List<? extends Shark> sharks = new ArrayList<>();
Run Code Online (Sandbox Code Playgroud)
为什么你不能在这个列表中添加扩展"Shark"的对象?喜欢:
sharks.add(new HammerShark());//will result in compilation error
Run Code Online (Sandbox Code Playgroud)
由于您在运行时有一个可以是A,B或C类型的列表,因此您无法在其中添加任何类型为A,B或C的对象,因为您最终可能会得到java中不允许的组合.
实际上,编译器确实可以在编译时看到你添加一个B:
sharks.add(new HammerShark());
Run Code Online (Sandbox Code Playgroud)
...但是无法判断在运行时,您的B将是列表类型的子类型还是超类型.在运行时,列表类型可以是A,B,C类型中的任何一种.因此,您最终无法在DeadHammerShark列表中添加HammerSkark(超类型).
*你会说:"好的,但为什么我不能在其中添加HammerSkark,因为它是最小的类型?".答:这是你知道的最小的.购买HammerSkark也可以被其他人扩展,你最终会遇到同样的情况.
让我们澄清一下CS - Consumer Super:
在同一层次结构中,我们可以尝试这样:
List<? super Shark> sharks = new ArrayList<>();
Run Code Online (Sandbox Code Playgroud)
您可以添加到此列表的内容和原因?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
Run Code Online (Sandbox Code Playgroud)
你可以添加上述类型的对象,因为鲨鱼(A,B,C)下面的任何东西都将是鲨鱼(X,Y,Z)之上的任何东西的子类型.容易明白.
您不能在Shark上面添加类型,因为在运行时,添加对象的类型可以在层次结构中高于列表的声明类型(X,Y,Z).这是不允许的.
但为什么你不能从这个列表中读取?(我的意思是你可以从中获取一个元素,但你不能将它分配给Object o以外的任何东西):
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
Run Code Online (Sandbox Code Playgroud)
在运行时,列表的类型可以是A:X,Y,Z,......之上的任何类型.编译器可以编译您的赋值语句(这似乎是正确的)但是,在运行时,s(Animal)的类型可以更低层次结构比列表的声明类型(可能是Creature,或更高).这是不允许的.
总结一下
我们使用<? super T>
在列表中添加等于或小于T的类型的对象.我们无法从中读取.
我们用来<? extends T>
从列表中读取等于或低于T的类型的对象.我们无法添加元素.
PECS“规则”只是确保以下内容是合法的:
?
是什么,都可以在法律上指 T
?
是什么,都可以合法地引用 T
典型的配对List<? extends T> producer, List<? super T> consumer
只是确保编译器可以强制执行标准的“IS-A”继承关系规则。如果我们可以合法地这样做,那么说起来可能更简单<T extends ?>, <? extends T>
(或者在 Scala 中更好,正如您在上面看到的那样,它是[-T], [+T]
。不幸的是,我们能做的最好的是<? super T>, <? extends T>
.
当我第一次遇到这个并在我的脑海中分解它时,机制是有道理的,但代码本身对我来说仍然令人困惑 - 我一直在想“似乎边界不应该像那样倒置” - 即使我上面很清楚 - 这只是为了保证遵守标准参考规则。
帮助我的是使用普通作业作为类比来看待它。
考虑以下(非生产就绪)玩具代码:
// copies the elements of 'producer' into 'consumer'
static <T> void copy(List<? extends T> producer, List<? super T> consumer) {
for(T t : producer)
consumer.add(t);
}
Run Code Online (Sandbox Code Playgroud)
在分配比喻而言示出这一点,consumer
该?
通配符(未知类型)为基准-的分配的“左手侧” -和<? super T>
保证,无论?
是,T
“IS-A” ?
-这T
可以被分配给它,因为?
是与T
.
对于producer
相同的问题,它只是颠倒了:producer
的?
通配符(未知类型)是指代- 分配的“右侧” - 并<? extends T>
确保无论?
是?
“IS-A” T
-它都可以分配给T
, 因为?
是T
.
让我们试着把这个概念形象化。
<? super SomeType>
是“未定义(尚未)”类型,但该未定义类型应该是“SomeType”类的超类。
也是如此<? extends SomeType>
。它是一种应该扩展“SomeType”类的类型(它应该是“SomeType”类的子类)。
如果我们考虑维恩图中“类继承”的概念,示例如下:
哺乳动物类扩展动物类(动物类是一个超级Mammal 类类)。
猫/狗类扩展了哺乳动物类(哺乳动物类是一个超级Cat/Dog 类类)。
然后,让我们将上图中的“圆圈”视为具有物理体积的“盒子”。
你不能把一个更大的盒子放进一个更小的盒子里。
您只能将较小的盒子放入较大的盒子中。
当您说 时<? super SomeType>
,您想描述一个与“SomeType”框大小相同或更大的“框”。
如果您说<? extends SomeType>
,那么您想描述一个与“SomeType”框大小相同或更小的“框”。
那么什么是PECS呢?
“生产者”的一个例子是我们只从中读取的列表。
“消费者”的一个例子是我们只写入的列表。
请记住这一点:
我们从“制作人”那里“阅读”,然后把这些东西放进我们自己的盒子里。
我们将自己的盒子“写入”到“消费者”中。
因此,我们需要从“生产者”那里读取(获取)一些东西并将其放入我们的“盒子”中。这意味着,从生产者采取的任何箱应不大于我们的“盒子”更大。这就是为什么“ P roducer ê xtends。”
“扩展”意味着一个较小的盒子(上面维恩图中较小的圆圈)。生产商的盒子应该比我们自己的盒子小,因为我们将从生产商那里拿走这些盒子,然后把它们放进我们自己的盒子里。我们不能放任何比我们的盒子大的东西!
此外,我们需要将我们自己的“盒子”写入(放入)到“消费者”中。这意味着消费者的盒子不应该比我们自己的盒子小。这就是为什么“ Ç onsumer小号UPER。”
“超级”意味着更大的盒子(上面维恩图中更大的圆圈)。如果我们要把自己的盒子放到一个消费者里面,消费者的盒子应该比我们的盒子大!
现在我们可以很容易地理解这个例子:
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));
}
}
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,我们想要读取(获取)一些东西src
并将它们写入(放入)到dest
. 所以这src
是一个“生产者”,它的“盒子”应该比某种类型更小(更具体)T
。
反之亦然,这dest
是一个“消费者”,它的“盒子”应该比某种类型更大(更通用)T
。
如果 的“盒子”src
比 的大dest
,我们就不能把那些大盒子放进小盒子里dest
。
如果有人读这一点,我希望它可以帮助您更好地理解“ P roducer ê xtends,Ç onsumer小号UPER。”
快乐编码!:)
归档时间: |
|
查看次数: |
93647 次 |
最近记录: |