协慌网

登录 贡献 社区

什么是 PECS(生产者扩展了超级消费者)?

在阅读泛型时,我遇到了 PECS( Producer extends和 Consumer super

有人可以向我解释如何使用 PECS 解决extendssuper之间的混淆吗?

答案

tl; dr: “PECS” 是从集合的角度来看的。如果您仅从通用集合中提取项目,则它是生产者,应使用extends ;如果您仅将物品塞入其中,则它是消费者,您应该使用super 。如果您都使用同一个集合,则不应同时使用extendssuper


假设您有一个方法以事物的集合为参数,但是您希望它比仅接受Collection<Thing>更为灵活。

情况 1:您想浏览集合并为每个项目做事。
那么列表是生产者,因此您应该使用Collection<? extends Thing>

原因是Collection<? extends Thing>可以包含Thing任何子类型,因此执行操作时每个元素都将像Thing (实际上,您不能将任何东西(null 除外)添加到Collection<? extends Thing> ,因为您在运行时无法知道该集合包含Thing的特定子类型。)

情况 2:您想向集合中添加东西。
那么列表是一个使用者,因此您应该使用Collection<? super Thing>

这里的理由是,与Collection<? extends Thing>Collection<? super Thing>无论实际的参数化类型是什么, Collection<? super Thing>始终可以容纳Thing在这里,您不必关心列表中已有的内容,只要它可以添加Thing这是什么? super Thing保证。

计算机科学背后的原理被称为

  • 协方差: ? extends MyClass
  • 逆变: ? super MyClass
  • 不变 / 不变: MyClass

下图应解释该概念。图片提供: Andrey Tyukin

协方差vs协方差

PECS(生产者extends和消费者super

助记符→获取和放置原则

原则指出:

  • 当您仅从结构中获取值时,请使用extends
  • 仅将值放入结构时,请使用super
  • 而且当您得到和放置时都不要使用通配符。

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)指出 “程序中的对象应该可以用其子类型的实例替换,而不会改变该程序的正确性”。

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

  • 如果它保留类型(≤)的顺序,则为协变,它将类型从更具体的类型归类为更通用的类型;
  • 逆变如果它反转这个排序;
  • 如果这两个都不适用,则为不变或不变。

协方差和协方差

  • 只读数据类型(源)可以是 协变的
  • 只写数据类型(接收器)可以是逆向的
  • 既充当源又充当宿的可变数据类型应该是不变的

为了说明这种一般现象,请考虑数组类型。对于动物类型,我们可以将动物类型 []

  • 协变:Cat [] 是动物 [];
  • 相反:Animal [] 是 Cat [];
  • 不变量:Animal [] 不是 Cat [],Cat [] 不是 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

有界(即朝某个地方去)通配符:有 3 种不同的通配符类型:

  • 在方差 / 非方差: ?还是? extends Object -无界通配符。它代表所有类型的家庭。当您得到和放置时使用。
  • 协方差: ? extends T (它们的亚型的所有类型的家庭T ) - 具有上结合的一个通配符。 T是在继承层次- 大多数类。仅当从结构中获取值时,请使用extends
  • 魂斗罗方差: ? super T (所有类型的家庭都是T超类)- 具有下界的通配符。 T是继承层次结构中最底层的类。仅将值放入结构时,请使用super

注意:通配符?表示零或一次,表示未知类型。通配符可以用作参数的类型,而不能用作泛型方法调用,泛型类实例创建的类型参数。(即,当使用通配符时,引用的引用未在程序的其他地方使用,例如我们使用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/1697099
https://stackoverflow.com/a/64888058/1697099

  • 协方差:接受子类型(只读,即生产者)
  • 矛盾:接受超类型(只写即消费者)