我在阅读泛型时遇到了PECS(Producer extends和Consumer super的缩写)。
有人能向我解释一下如何使用PECS来解决extends和super之间的混淆吗?
我在阅读泛型时遇到了PECS(Producer extends和Consumer super的缩写)。
有人能向我解释一下如何使用PECS来解决extends和super之间的混淆吗?
当前回答
PECS(生产者延伸和消费者超级)
[协方差和反方差]
让我们来看看示例
public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }
泛型允许您以安全的方式动态处理类型
//ListA
List<A> listA = new ArrayList<A>();
//add
listA.add(new A());
listA.add(new B());
listA.add(new C());
//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListB
List<B> listB = new ArrayList<B>();
//add
listB.add(new B());
//get
B b0 = listB.get(0);
问题
由于Java的Collection是一种引用类型,因此我们面临以下问题:
问题#1
//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;
*Swift的泛型没有这样的问题,因为Collection是Value类型[About],因此创建了一个新的集合
问题#2
//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;
解决方案-通用通配符
通配符是引用类型功能,不能直接实例化
解决方案#1<? super A>又称下界,又称反方差,又称消费者保证它由A和所有超类操作,这就是为什么添加
List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();
//add
listSuperA.add(new A());
listSuperA.add(new B());
//get
Object o0 = listSuperA.get(0);
解决方案#2
<? 扩展A>aka上界aka协方差aka生产者保证它由A和所有子类操作,这就是为什么它是安全的
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
其他回答
这是我认为extends与super最清晰、最简单的方式:
扩展用于读取super是用来写作的
我发现“PECS”是一种不明显的方式来思考谁是“生产者”,谁是“消费者”。“PECS”是从数据集合本身的角度定义的——如果对象正在被写入到集合中,则集合“消耗”(它消耗来自调用代码的对象),如果对象正在从集合中读取,则它“产生”(它向某些调用代码产生对象)。这与其他所有事物的命名方式相反。标准JavaAPI是从调用代码的角度命名的,而不是从集合本身命名的。例如,java.util.List的以集合为中心的视图应该有一个名为“receive()”的方法,而不是“add()”——毕竟,调用代码添加了元素,但列表本身接收了元素。
我认为从与集合交互的代码的角度来思考事情更直观、更自然、更一致——代码是“从集合中读取”还是“写入”集合?之后,向集合写入的任何代码都将是“生产者”,从集合读取的任何代码将是“消费者”。
让我们假设这个层次结构:
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
让我们澄清PE-Producer扩展:
List<? extends Shark> sharks = new ArrayList<>();
为什么不能在此列表中添加扩展“Shark”的对象?如:
sharks.add(new HammerShark());//will result in compilation error
由于您有一个在运行时可以是a、B或C类型的列表,因此您不能在其中添加任何a、B和C类型的对象,因为您可能会得到一个在java中不允许的组合。实际上,编译器确实可以在编译时看到您添加了一个B:
sharks.add(new HammerShark());
…但它无法确定在运行时,您的B是列表类型的子类型还是超类型。在运行时,列表类型可以是A、B、C中的任何一种类型。因此,例如,您不能在DeadHammerShark列表中添加HammerSkark(超级类型)。
*你会说:“好吧,但既然它是最小的类型,为什么我不能在其中添加HammerSkark?”。答:这是你知道的最小的。但HammerSkark也可以被其他人扩展,你最终也会遇到同样的情况。
让我们来澄清一下CS-超级消费者:
在同一层次结构中,我们可以尝试以下操作:
List<? super Shark> sharks = new ArrayList<>();
您可以向此列表中添加什么以及为什么?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
您可以添加上述类型的对象,因为shark(A、B、C)以下的任何对象都将始终是shark(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
在运行时,列表的类型可以是A:X、Y、Z。。。编译器可以编译赋值语句(这似乎是正确的),但在运行时,s(Animal)的类型在层次结构上可以低于列表的声明类型(可以是Creature,也可以更高)。这是不允许的。
综上所述
我们使用<?super T>将类型等于或低于T的对象添加到列表中。我们无法阅读它我们使用<?扩展T>以从列表中读取类型等于或低于T的对象。我们不能向其中添加元素。
协方差:接受子类型相反:接受超类型
协变类型是只读的,而逆变类型是只写的。
tl;博士:“PECS”是从藏品的角度来看的。如果您只是从泛型集合中提取项,那么它是一个生产者,您应该使用extends;如果你只是在填充物品,那么它是一个消费者,你应该使用super。如果同时使用同一集合,则不应使用extends或super。
假设您有一个方法,它以一个集合作为参数,但您希望它比只接受集合<Thing>更灵活。
案例1:你想通过收集并处理每个项目。那么列表是一个生产者,因此您应该使用集合<?扩展Thing>。
理由是集合<?extendsThing>可以保存Thing的任何子类型,因此当您执行操作时,每个元素都将表现为Thing。(实际上不能向集合中添加任何内容(null除外)<?扩展Thing>,因为您无法在运行时知道集合持有Thing的哪个特定子类型。)
案例2:您想要向集合中添加内容。那么列表是消费者,因此您应该使用集合<?超级事物>。
这里的理由是,不同于集合<?扩展Thing>,Collection<?super Thing>可以始终保存Thing,无论实际参数化类型是什么。在这里,只要它允许添加Thing,您就不在乎列表中已经存在什么;这是什么?super Thing保证。
简而言之,要记住PECS的三个简单规则:
使用<?如果需要检索对象集合中的类型T。使用<?如果需要将T类型的对象放入一个集合。如果您需要同时满足这两个条件,那么不要使用通配符。像就这么简单。