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

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


当前回答

协方差:接受子类型相反:接受超类型

协变类型是只读的,而逆变类型是只写的。

其他回答

使用现实生活中的示例(有一些简化):

想象一列有货车的货运火车,类似于一个列表。如果货物的尺寸与货车相同或更小,您可以将货物放入货车=<?超级货车尺寸>如果您的仓库有足够的空间(大于货物的大小),您可以从货运车上卸载货物=<?扩展DepotSize>

正如我在回答另一个问题时所解释的,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“规则”仅确保以下内容合法:

消费者:什么?它可以合法地指代T制片人:什么?是的,它可以在法律上被T

列表中的典型配对<?扩展T>生产者,列表<?super T>使用者只是确保编译器可以强制执行标准的“is-A”继承关系规则。如果我们可以合法地这样做,那么说<T extends?>、<?extended T>(或者在Scala中更好,如您所见,它是[-T],[+T]。不幸的是,我们能做的最好的是<?super T>,<?extended 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);
}

用任务类比来说明这一点,对于消费者来说?通配符(未知类型)是引用-赋值的“左侧”-和<?超级T>确保了什么?T“is-A”?-T可以分配给它,因为?是T的超类型(或最多相同类型)。

对于制片人来说,他们的担忧是一样的,只是颠倒了:制片人的?通配符(未知类型)是指代-赋值的“右手边”-和<?扩展T>确保了什么?是“IS-A”T-它可以分配给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);
        }
    }

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

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