我在阅读泛型时遇到了PECS(Producer extends和Consumer super的缩写)。
有人能向我解释一下如何使用PECS来解决extends和super之间的混淆吗?
我在阅读泛型时遇到了PECS(Producer extends和Consumer super的缩写)。
有人能向我解释一下如何使用PECS来解决extends和super之间的混淆吗?
当前回答
(添加答案,因为使用泛型通配符的示例永远不够)
// 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);
}
}
其他回答
正如我在回答另一个问题时所解释的,PECS是乔什·布洛克(Josh Bloch)为帮助记住Producer extends和Consumer super而创建的助记符设备。
这意味着当传递给方法的参数化类型将生成T的实例(它们将以某种方式从中检索)时?应该使用extends T,因为T的子类的任何实例也是T。当传递给方法的参数化类型将消耗T的实例(它们将传递给它以执行某些操作)时?应该使用super T,因为T的实例可以合法地传递给任何接受某个T超类型的方法。例如,可以在Collection<Integer>上使用Comparator<Number>?extends T无法工作,因为Comparator<Integer>无法对Collection<Number>进行操作。
请注意,通常您只应使用?延伸T和?super T表示某些方法的参数。方法应该只使用T作为泛型返回类型的类型参数。
(添加答案,因为使用泛型通配符的示例永远不够)
// 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);
}
}
计算机科学背后的原理叫做
协方差:?扩展MyClass,矛盾:?超级MyClass和不变性/非方差:MyClass
下图应解释该概念。图片提供:Andrey Tyukin
让我们尝试可视化这个概念。
<? super SomeType>是一个“undefined(yet)”类型,但该未定义类型应该是“SomeType”类的超类。
<?扩展SomeType>。它是一个应该扩展“SomeType”类的类型(它应该是“SomeType”类的子类)。
如果我们在Venn图中考虑“类继承”的概念,示例如下:
哺乳动物类扩展了动物类(动物类是哺乳动物类的超类)。
猫/狗类扩展了哺乳动物类(哺乳动物类是猫/狗的超类)。
然后,让我们将上图中的“圆圈”视为具有物理体积的“盒子”。
你不能把更大的盒子放进更小的盒子里。
你只能把一个小盒子放进一个大盒子里。
当你说<?super SomeType>,你想描述一个与“SomeType”框大小相同或更大的“框”。
如果你说<?扩展SomeType>,那么您需要描述一个与SomeType框大小相同或更小的“框”。
那么PECS到底是什么呢?
“生产者”的一个例子是我们只能从中读取的列表。
“消费者”的一个例子是我们只写入的列表。
请记住:
我们从“制片人”那里“阅读”,然后把这些东西带到我们自己的盒子里。我们把自己的盒子“写”成“消费者”。
因此,我们需要从“制片人”那里读到一些东西,并将其放入我们的“盒子”中。这意味着,从生产商那里拿走的任何盒子都不应该比我们的“盒子”大。这就是《制作人延伸》的原因
“延伸”是指一个较小的方框(上图中的较小圆圈)。制片人的盒子应该比我们自己的盒子小,因为我们要从制片人那里拿走这些盒子,然后把它们放进自己的盒子里。我们不能放比我们的箱子更大的东西!
此外,我们需要将自己的“盒子”写入“消费者”。这意味着消费者的箱子不应小于我们自己的箱子。这就是为什么“超级消费者”
“超级”是指一个更大的盒子(上图中的大圆圈)。如果我们想把自己的盒子放进消费者手中,消费者的盒子应该比我们的盒子大!
现在我们很容易理解这个例子:
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));
}
}
在上面的示例中,我们希望从src中读取(获取)一些内容,并将其写入(放入)dest。因此src是一个“Producer”,它的“box”应该比一些类型T小(更具体)。
反之亦然,dest是一个“消费者”,它的“盒子”应该比某些类型的T更大(更一般)。
如果src的“盒子”比dest的大,我们就不能把这些大盒子放进dest的小盒子里。
如果有人读到这篇文章,我希望它能帮助你更好地理解“生产者延伸,消费者超级。”
快乐编码!:)
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);