Java 什么是PECS(生产者延伸消费者超级)?

Java 什么是PECS(生产者延伸消费者超级)?,java,generics,super,bounded-wildcard,pecs,Java,Generics,Super,Bounded Wildcard,Pecs,在阅读泛型时,我遇到了PEC(生产者扩展和消费者超级) 有人能给我解释一下如何使用PEC来解决extends和super之间的混淆吗?正如我在另一个问题中解释的那样,PEC是Josh Bloch创建的一种助记装置,用于帮助记忆producerextends,Consumersuper 这意味着,当一个参数化类型被传递给一个方法时,将产生T的实例(它们将以某种方式从中检索),?应该使用extends T,因为T的子类的任何实例也是T 当传递给方法的参数化类型将使用T的实例时(它们将被传递给它以执行

在阅读泛型时,我遇到了PEC(生产者
扩展
和消费者
超级

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

正如我在另一个问题中解释的那样,PEC是Josh Bloch创建的一种助记装置,用于帮助记忆producer
extends
Consumer
super

这意味着,当一个参数化类型被传递给一个方法时,将产生
T
的实例(它们将以某种方式从中检索),
?应该使用extends T
,因为
T
的子类的任何实例也是
T

当传递给方法的参数化类型将使用
T
的实例时(它们将被传递给它以执行某些操作),
?应该使用super T
,因为
T
的实例可以合法地传递给任何接受某种超类型
T
的方法。例如,
比较器可以用于
集合
<代码>?extends T
无法工作,因为
比较器无法对
集合进行操作

请注意,通常只应使用
?扩展T
?super T
用于某些方法的参数。方法应该只使用
T
作为泛型返回类型的类型参数。

tl;dr:“PECS”是从该系列的角度来看的。如果您只是从通用集合中提取项目,则它是生产者,您应该使用
扩展
;如果您只是在填充项目,则它是消费者,您应该使用
super
。如果对同一个集合同时使用这两种方法,则不应使用
extends
super


假设您有一个方法,该方法将一个事物集合作为其参数,但您希望它比只接受一个
集合
更灵活

案例1:您希望浏览收藏并处理每一项。
然后列表是一个生产者,因此您应该使用
集合
公共类测试{
公共A类{}
公共类B扩展了{}
公共类C扩展了B{}

在计算机科学中,这背后的原理被称为

  • 协方差:
    ?扩展MyClass
  • 相反:
    ?超级MyClass
  • 不变性/非方差:
    MyClass
下面的图片应该解释这个概念。 图片提供:


简而言之,记住PEC的三条简单规则:

  • 使用PEC(生产者<代码>扩展
  • 和消费者<代码>超级

    记忆的→ 获取并放置原则

    该原则规定:

    • 仅从结构中获取值时,请使用
      扩展
      通配符
    • 仅将值放入结构时,请使用
      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)规定,“程序中的对象应可替换为其子类型的实例,而不会改变该程序的正确性。”

    在编程语言的类型系统中,一种类型规则

    • 协变的如果它保持类型的顺序(≤), 从更具体到更一般的订单类型
    • 逆变如果它颠倒了顺序
    • 不变的或非变化的(如果两者都不适用)

    • 只读数据类型(源)可以是
    • 仅写数据类型(接收器)可以是逆变的
    • 同时作为源和汇的可变数据类型应该是不变的

    为了说明这种普遍现象,考虑数组类型。对于类型动物,我们可以做类型动物[] /P>

    • 协变的:猫是动物
    • 逆变型:动物[]是猫[]
    • 不变量:动物[]不是猫[],猫[]也不是动物[]
    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  
    

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


    • 协方差:接受子类型(只读,即生产者)
    • 相反:接受超类型(仅写,即消费者)
    (添加答案,因为使用泛型通配符的示例永远不够多)

    //源代码
    List intList=Arrays.asList(1,2,3);
    List doubleList=Arrays.asList(2.78,3.14);
    List numList=Arrays.asList(1,2,2.78,3.14,5);
    //目的地
    List intList2=new ArrayList();
    List doublesList2=新的ArrayList();
    List numList2=new ArrayList();
    //工作
    copyElements1(intList,intList2);//从int到int
    copyElements1(doubleList,doublesList2);//从double到double
    静态void copyements1(集合src、集合dest){
    对于(T n:src){
    目的地添加(n);
    }
    }
    //让我们尝试将intList复制到它的超类型
    copyElements1(intList,numList2);//错误,方法签名只是说“T”
    
    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  
    
     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.
             */
        }
    }
    
           // 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
    
    List<? extends Shark> sharks = new ArrayList<>();
    
    sharks.add(new HammerShark());//will result in compilation error
    
    sharks.add(new HammerShark());
    
    List<? super Shark> sharks = new ArrayList<>();
    
    sharks.add(new Shark());
    sharks.add(new DeadHammerShark());
    sharks.add(new HammerSkark());
    
    Object o;
    o = sharks.get(2);// only assignment that works
    
    Animal s;
    s = sharks.get(2);//doen't work
    
    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);
    
    //not compiled
    //danger of **adding** non-B objects using listA reference
    listA = listB;
    
    //not compiled
    //danger of **getting** non-B objects using listB reference
    listB = listA;
    
    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);
    
    List<? extends A> listExtendsA;
    listExtendsA = listA;
    listExtendsA = listB;
    
    //get
    A a0 = listExtendsA.get(0);
    
    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)); 
      } 
    }