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

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


当前回答

tl;博士:“PECS”是从藏品的角度来看的。如果您只是从泛型集合中提取项,那么它是一个生产者,您应该使用extends;如果你只是在填充物品,那么它是一个消费者,你应该使用super。如果同时使用同一集合,则不应使用extends或super。


假设您有一个方法,它以一个集合作为参数,但您希望它比只接受集合<Thing>更灵活。

案例1:你想通过收集并处理每个项目。那么列表是一个生产者,因此您应该使用集合<?扩展Thing>。

理由是集合<?extendsThing>可以保存Thing的任何子类型,因此当您执行操作时,每个元素都将表现为Thing。(实际上不能向集合中添加任何内容(null除外)<?扩展Thing>,因为您无法在运行时知道集合持有Thing的哪个特定子类型。)

案例2:您想要向集合中添加内容。那么列表是消费者,因此您应该使用集合<?超级事物>。

这里的理由是,不同于集合<?扩展Thing>,Collection<?super Thing>可以始终保存Thing,无论实际参数化类型是什么。在这里,只要它允许添加Thing,您就不在乎列表中已经存在什么;这是什么?super Thing保证。

其他回答

请记住:

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

tl;博士:“PECS”是从藏品的角度来看的。如果您只是从泛型集合中提取项,那么它是一个生产者,您应该使用extends;如果你只是在填充物品,那么它是一个消费者,你应该使用super。如果同时使用同一集合,则不应使用extends或super。


假设您有一个方法,它以一个集合作为参数,但您希望它比只接受集合<Thing>更灵活。

案例1:你想通过收集并处理每个项目。那么列表是一个生产者,因此您应该使用集合<?扩展Thing>。

理由是集合<?extendsThing>可以保存Thing的任何子类型,因此当您执行操作时,每个元素都将表现为Thing。(实际上不能向集合中添加任何内容(null除外)<?扩展Thing>,因为您无法在运行时知道集合持有Thing的哪个特定子类型。)

案例2:您想要向集合中添加内容。那么列表是消费者,因此您应该使用集合<?超级事物>。

这里的理由是,不同于集合<?扩展Thing>,Collection<?super Thing>可以始终保存Thing,无论实际参数化类型是什么。在这里,只要它允许添加Thing,您就不在乎列表中已经存在什么;这是什么?super Thing保证。

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

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

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

在处理集合时,在上限或下限通配符之间进行选择的常见规则是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)