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

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


当前回答

这是我认为extends与super最清晰、最简单的方式:

扩展用于读取super是用来写作的

我发现“PECS”是一种不明显的方式来思考谁是“生产者”,谁是“消费者”。“PECS”是从数据集合本身的角度定义的——如果对象正在被写入到集合中,则集合“消耗”(它消耗来自调用代码的对象),如果对象正在从集合中读取,则它“产生”(它向某些调用代码产生对象)。这与其他所有事物的命名方式相反。标准JavaAPI是从调用代码的角度命名的,而不是从集合本身命名的。例如,java.util.List的以集合为中心的视图应该有一个名为“receive()”的方法,而不是“add()”——毕竟,调用代码添加了元素,但列表本身接收了元素。

我认为从与集合交互的代码的角度来思考事情更直观、更自然、更一致——代码是“从集合中读取”还是“写入”集合?之后,向集合写入的任何代码都将是“生产者”,从集合读取的任何代码将是“消费者”。

其他回答

让我们尝试可视化这个概念。

<? 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是乔什·布洛克(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的子类型(或至少相同类型)。

在处理集合时,在上限或下限通配符之间进行选择的常见规则是PECS。信用

PECS(生产者延伸和消费者超级)

助记符→ 获取(扩展)和放置(超级)原则。

该原则规定:仅从结构中获取值时,请使用扩展通配符。仅将值放入结构中时,请使用超级通配符。当你得到和得到时,不要使用通配符。

Java示例:

class Super {
        Number testCoVariance() {
            return null;
        }
        void testContraVariance(Number parameter) {
        } 
    }
    
    class Sub extends Super {
        @Override
        Integer testCoVariance() {
            return null;
        } //compiles successfully i.e. return type is don't care(Integer is subtype of Number)
        @Override
        void testContraVariance(Integer parameter) {
        } //doesn't support even though Integer is subtype of Number
    }

Liskov替换原则(LSP)指出,“程序中的对象应该用其子类型的实例替换,而不改变程序的正确性”。

在编程语言的类型系统中,键入规则

协变的,如果它保持类型的排序(≤),则将类型从更具体到更通用排序;相反,如果它颠倒了这个顺序;如果两者都不适用,则为不变或非变。

协方差和反方差

只读数据类型(源)可以是协变的;只写数据类型(汇)可以是相反的。充当源和汇的可变数据类型应该是不变的。

要说明这种一般现象,请考虑数组类型。对于Animal类型,我们可以使用Animal[]类型

协变:猫[]是动物[];反义词:动物[]是猫[];不变量:动物[]不是猫[],猫[]不是动物[]。

Java示例:

Object name= new String("prem"); //works
List<Number> numbers = new ArrayList<Integer>();//gets compile time error

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
myNumber[0] = 3.14; //attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)

List<String> list=new ArrayList<>();
list.add("prem");
List<Object> listObject=list; //Type mismatch: cannot convert from List<String> to List<Object> at Compiletime  

更多示例

图像src

有界(即朝向某处)通配符:有三种不同的通配符:

差异/非差异:?或扩展对象-无边界通配符。它代表所有类型的家庭。当你得到和投入时使用。共方差:?扩展T(T子体的统治)-带有上限的通配符。T是继承层次结构中最上层的类。仅从结构中获取值时,请使用扩展通配符。抵销方差:?超级T(T祖先的统治)-带有下限的通配符。T是继承层次结构中最底层的类。当只将值放入结构中时,请使用超级通配符。

注意:通配符?表示零或一次,表示未知类型。通配符可以用作参数的类型,而不能用作泛型方法调用或泛型类实例创建的类型参数。(即,当使用通配符时,引用不在程序的其他地方使用,如我们使用T)

 import java.util.ArrayList;
import java.util.List;

class Shape { void draw() {}}

class Circle extends Shape {void draw() {}}

class Square extends Shape {void draw() {}}

class Rectangle extends Shape {void draw() {}}

public class Test {

    public static void main(String[] args) {
        //? extends Shape i.e. can use any sub type of Shape, here Shape is Upper Bound in inheritance hierarchy
        List<? extends Shape> intList5 = new ArrayList<Shape>();
        List<? extends Shape> intList6 = new ArrayList<Cricle>();
        List<? extends Shape> intList7 = new ArrayList<Rectangle>();
        List<? extends Shape> intList9 = new ArrayList<Object>();//ERROR.


        //? super Shape i.e. can use any super type of Shape, here Shape is Lower Bound in inheritance hierarchy
        List<? super Shape> inList5 = new ArrayList<Shape>();
        List<? super Shape> inList6 = new ArrayList<Object>();
        List<? super Shape> inList7 = new ArrayList<Circle>(); //ERROR.

        //-----------------------------------------------------------
        Circle circle = new Circle();
        Shape shape = circle; // OK. Circle IS-A Shape

        List<Circle> circles = new ArrayList<>();
        List<Shape> shapes = circles; // ERROR. List<Circle> is not subtype of List<Shape> even when Circle IS-A Shape

        List<? extends Circle> circles2 = new ArrayList<>();
        List<? extends Shape> shapes2 = circles2; // OK. List<? extends Circle> is subtype of List<? extends Shape>


        //-----------------------------------------------------------
        Shape shape2 = new Shape();
        Circle circle2= (Circle) shape2; // OK. with type casting

        List<Shape> shapes3 = new ArrayList<>();
        List<Circle> circles3 = shapes3; //ERROR. List<Circle> is not subtype of  List<Shape> even Circle is subetype of Shape

        List<? super Shape> shapes4 = new ArrayList<>();
        List<? super Circle> circles4 = shapes4; //OK.
    }

    
    
    /*
     * Example for an upper bound wildcard (Get values i.e Producer `extends`)
     *
     * */
    public void testCoVariance(List<? extends Shape> list) {
        list.add(new Object());//ERROR
        list.add(new Shape()); //ERROR
        list.add(new Circle()); // ERROR
        list.add(new Square()); // ERROR
        list.add(new Rectangle()); // ERROR
        Shape shape= list.get(0);//OK so list act as produces only
    /*
     * You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
     * You can get an object and know that it will be an Shape
     */
    }
    
    
    /*
     * Example for  a lower bound wildcard (Put values i.e Consumer`super`)
     * */
    public void testContraVariance(List<? super Shape> list) {
        list.add(new Object());//ERROR
        list.add(new Shape());//OK
        list.add(new Circle());//OK
        list.add(new Square());//OK
        list.add(new Rectangle());//OK
        Shape shape= list.get(0); // ERROR. Type mismatch, so list acts only as consumer
        Object object= list.get(0); //OK gets an object, but we don't know what kind of Object it is.
        /*
         * You can add a Shape,Circle,Square,Rectangle to a List<? super Shape>
         * You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
         */
    }
}

泛型和示例

协方差和反方差根据类型确定兼容性。在任何一种情况下,方差都是有向关系。协方差可以翻译为“同一方向不同”或不同,而反方差意味着“相反方向不同”,或相反。协变型和逆变型并不相同,但它们之间存在相关性。这些名称暗示了相关性的方向。

https://stackoverflow.com/a/54576828/1697099https://stackoverflow.com/a/64888058/1697099

协方差:接受子类型(只读,即Producer)相反:接受超类型(仅写,即Consumer)

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

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