我在阅读泛型时遇到了PECS(Producer extends和Consumer super的缩写)。

有人能向我解释一下如何使用PECS来解决extends和super之间的混淆吗?


当前回答

正如我在回答另一个问题时所解释的,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作为泛型返回类型的类型参数。

其他回答

请记住:

消费者吃晚餐(超级);生产商扩大了其母公司的工厂

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);
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
    }
}

(添加答案,因为使用泛型通配符的示例永远不够)

       // 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);
        }
    }

让我们假设这个层次结构:

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的对象。我们不能向其中添加元素。