我在阅读泛型时遇到了PECS(Producer extends和Consumer super的缩写)。
有人能向我解释一下如何使用PECS来解决extends和super之间的混淆吗?
我在阅读泛型时遇到了PECS(Producer extends和Consumer super的缩写)。
有人能向我解释一下如何使用PECS来解决extends和super之间的混淆吗?
当前回答
简而言之,要记住PECS的三个简单规则:
使用<?如果需要检索对象集合中的类型T。使用<?如果需要将T类型的对象放入一个集合。如果您需要同时满足这两个条件,那么不要使用通配符。像就这么简单。
其他回答
使用现实生活中的示例(有一些简化):
想象一列有货车的货运火车,类似于一个列表。如果货物的尺寸与货车相同或更小,您可以将货物放入货车=<?超级货车尺寸>如果您的仓库有足够的空间(大于货物的大小),您可以从货运车上卸载货物=<?扩展DepotSize>
这是我认为extends与super最清晰、最简单的方式:
扩展用于读取super是用来写作的
我发现“PECS”是一种不明显的方式来思考谁是“生产者”,谁是“消费者”。“PECS”是从数据集合本身的角度定义的——如果对象正在被写入到集合中,则集合“消耗”(它消耗来自调用代码的对象),如果对象正在从集合中读取,则它“产生”(它向某些调用代码产生对象)。这与其他所有事物的命名方式相反。标准JavaAPI是从调用代码的角度命名的,而不是从集合本身命名的。例如,java.util.List的以集合为中心的视图应该有一个名为“receive()”的方法,而不是“add()”——毕竟,调用代码添加了元素,但列表本身接收了元素。
我认为从与集合交互的代码的角度来思考事情更直观、更自然、更一致——代码是“从集合中读取”还是“写入”集合?之后,向集合写入的任何代码都将是“生产者”,从集合读取的任何代码将是“消费者”。
协方差:接受子类型相反:接受超类型
协变类型是只读的,而逆变类型是只写的。
简而言之,要记住PECS的三个简单规则:
使用<?如果需要检索对象集合中的类型T。使用<?如果需要将T类型的对象放入一个集合。如果您需要同时满足这两个条件,那么不要使用通配符。像就这么简单。
让我们假设这个层次结构:
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的对象。我们不能向其中添加元素。