首页 文章

什么是PECS(制作人扩展消费者超级)?

提问于
浏览
587

我在阅读泛型时遇到了PECS(制片人 extends 和消费者 super 的简称) .

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

12 回答

  • 2

    记住这一点:

    消费者吃晚餐(超级);制片人扩展了他父母的工厂

  • 468

    tl;dr: "PECS"来自集合的观点 . 如果您只是从通用集合中提取项目,那么它是 生产环境 者,您应该使用 extends ;如果你只是填充物品,它是一个消费者,你应该使用 super . 如果同时使用相同的集合,则不应使用 extendssuper .


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

    Case 1: You want to go through the collection and do things with each item.
    然后列表是 producer ,所以你应该使用 Collection<? extends Thing> .

    原因是 Collection<? extends Thing> 可以保存 Thing 的任何子类型,因此当您执行操作时,每个元素都将表现为 Thing . (您实际上无法向 Collection<? extends Thing> 添加任何内容,因为您无法在运行时知道集合中包含的 Thing 的哪个特定子类型 . )

    Case 2: You want to add things to the collection.
    然后列表是 consumer ,所以你应该使用 Collection<? super Thing> .

    这里的推理是,与 Collection<? extends Thing> 不同,无论实际参数化类型是什么, Collection<? super Thing> 总是可以保持 Thing . 在这里你不关心列表中已有的内容,只要它允许添加 Thing ;这是 ? super Thing 的保证 .

  • 681

    计算机科学背后的原理以其命名

    • 协方差 - ?扩展MyClass,

    • 逆变 - ?超级MyClass和

    • 不变性/非差异 - MyClass

    下面的图片应该解释这个概念 .

    图片礼貌:Andrey Tyukin

    Covariance vs Contravariance

  • 27

    PECS(“ Producer extends and Consumer super ”的缩写)可以解释为: Get and Put Principle

    获取和放置原则(来自Java泛型和集合)

    它指出,

    当你只有一个结构中的 get 值时,

    • 使用 extends wildcard
      当你只有 put 值进入一个结构时
    • 使用 super wildcard
      _999_和 don’t use a wildcard 当你 both get and put .

    让我们通过例子来理解它:

    1. For Extends Wildcard(get values i.e Producer extends)

    这是一个方法,它采用一组数字,将每个数字转换为 double ,并将它们相加

    public static double sum(Collection<? extends Number> nums) {
       double s = 0.0;
       for (Number num : nums) 
          s += num.doubleValue();
       return s;
    }
    

    我们称之为方法:

    List<Integer>ints = Arrays.asList(1,2,3);
    assert sum(ints) == 6.0;
    List<Double>doubles = Arrays.asList(2.78,3.14);
    assert sum(doubles) == 5.92;
    List<Number>nums = Arrays.<Number>asList(1,2,2.78,3.14);
    assert sum(nums) == 8.92;
    

    sum() method uses extends 以来,以下所有电话都是合法的 . 如果未使用扩展,前两个调用将不合法 .

    EXCEPTION :您 cannot put anything 进入使用 extends 通配符声明的类型 - 除了值 null ,它属于每个引用类型:

    List<Integer> ints = new ArrayList<Integer>();
    ints.add(1);
    ints.add(2);
    List<? extends Number> nums = ints;
    nums.add(null);  // ok
    assert nums.toString().equals("[1, 2, null]");
    

    2. For Super Wildcard(put values i.e Consumer super)

    这是一个方法,它采用数字和 int n 的集合,并将第一个 n 整数从零开始放入集合中:

    public static void count(Collection<? super Integer> ints, int n) {
        for (int i = 0; i < n; i++) ints.add(i);
    }
    

    我们称之为方法:

    List<Integer>ints = new ArrayList<Integer>();
    count(ints, 5);
    assert ints.toString().equals("[0, 1, 2, 3, 4]");
    List<Number>nums = new ArrayList<Number>();
    count(nums, 5); nums.add(5.0);
    assert nums.toString().equals("[0, 1, 2, 3, 4, 5.0]");
    List<Object>objs = new ArrayList<Object>();
    count(objs, 5); objs.add("five");
    assert objs.toString().equals("[0, 1, 2, 3, 4, five]");
    

    由于 count() method uses super ,以下所有呼叫都是合法的:如果未使用超级呼叫,则最后两次呼叫将不合法 .

    EXCEPTION :你 cannot get anything 来自用 super 通配符声明的类型 - 除了 Object 类型的值,它是每个引用类型的超类型:

    List<Object> objs = Arrays.<Object>asList(1,"two");
    List<? super Integer> ints = objs;
    String str = "";
    for (Object obj : ints) str += obj.toString();
    assert str.equals("1two");
    

    3. When both Get and Put, don't Use wildcard

    每当您将 both put 值分配到同一结构的 and get 值时,您就 should not use a wildcard .

    public static double sumCount(Collection<Number> nums, int n) {
       count(nums, n);
       return sum(nums);
    }
    
  • 27
    public class Test {
    
        public class A {}
    
        public class B extends A {}
    
        public class C extends B {}
    
        public void testCoVariance(List<? extends B> myBlist) {
            B b = new B();
            C c = new C();
            myBlist.add(b); // does not compile
            myBlist.add(c); // does not compile
            A a = myBlist.get(0); 
        }
    
        public void testContraVariance(List<? super B> myBlist) {
            B b = new B();
            C c = new C();
            myBlist.add(b);
            myBlist.add(c);
            A a = myBlist.get(0); // does not compile
        }
    }
    
  • 17

    PECS(制片人 extends 和消费者 super

    助记符→获取和放置原则 .

    该原则指出:

    • 当您只从结构中获取值时,请使用扩展通配符 .

    • 仅在将值放入结构时使用超级通配符 .

    • 并且当你得到和放置时不要使用通配符 .

    在Java中,参数和泛型类型参数不支持如下继承 .

    class Super {
        void testCoVariance(Object parameter){} // method Consumes the Object
        Object testContraVariance(){ return null;} //method Produces the Object
    }
    
    class Sub extends Super {
        @Override
        void testCoVariance(String parameter){} //doesn't support eventhough String is subtype of Object
    
        @Override
        String testContraVariance(){ return null;} //compiles successfully i.e. return type is don't care 
    }
    

    Liskov substitution principle: Arrays 是协变的(不安全的)但是泛型不是那么(不安全) . 即替换原则不适用于参数化类型,这意味着它是非法的 .
    协变只是意味着 XY 的子类型,那么 X[] 也将是 Y[] 的子类型 .

    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
    

    more examples

    bounded (即前往某处) wildcard :有3种不同的通配符:

    • 方差/非方差: ?? extends Object - Unbounded 通配符 . 它代表所有类型的家庭 . 当你得到和放置时使用 .

    • 协方差: ? extends TT 的子类型的所有类型的族) - 带有 upper bound 的通配符 . T 是继承层次结构中的 upper -most类 . 当结构中只有 Get 值时,请使用 extends 通配符 .

    • Contra-variance: ? super TT 的超类型的所有类型的族) - 带 lower bound 的通配符 . T 是继承层次结构中的 lower -most类 . 当只有 Put 值进入结构时,请使用 super 通配符 .

    注意:通配符 ? 表示 zero or one time ,表示未知类型 . 通配符可以用作参数的类型,从不用作泛型的类型参数方法调用,一个通用的类实例创建 . (即当使用通配符时,引用未在程序中的其他地方使用,就像我们使用 T

    enter image description here

    class Shape { void draw() {}}
    
    class Circle extends Shape {void draw() {}}
    
    class Square extends Shape {void draw() {}}
    
    class Rectangle extends Shape {void draw() {}}
    
    public class TestContraVariance {
     /*
       * Example for an upper bound wildcard (Get values i.e Producer `extends`)
       * 
       * */  
    
        public void testCoVariance(List<? extends Shape> list) {
            list.add(new Shape()); // Error:  is not applicable for the arguments (Shape) i.e. inheritance is not supporting
            list.add(new Circle()); // Error:  is not applicable for the arguments (Circle) i.e. inheritance is not supporting
            list.add(new Square()); // Error:  is not applicable for the arguments (Square) i.e. inheritance is not supporting
            list.add(new Rectangle()); // Error:  is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
            Shape shape= list.get(0);//compiles 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 Shape());//compiles i.e. inheritance is supporting
            list.add(new Circle());//compiles i.e. inheritance is  supporting
            list.add(new Square());//compiles i.e. inheritance is supporting
            list.add(new Rectangle());//compiles i.e. inheritance is supporting
            Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
            Object object= list.get(0); // 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<? extends Shape> 
            * You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
            */  
        }
    }
    

    genericsexamples

  • 4

    正如我在另一个问题中解释的那样,PECS是由Josh Bloch创建的助记符设备,用于帮助记住 P roducer extendsC onsumer super .

    这意味着当传递给方法的参数化类型将生成T的实例(它们将以某种方式从它中检索),?应该使用extends T,因为T的子类的任何实例也是T.当传递给方法的参数化类型将使用T的实例(它们将被传递给它做某事),?应该使用super T,因为T的实例可以合法地传递给任何接受某种超类型T的方法 . 例如,Comparator <Number>可以用于Collection <Integer> . ? extends T不起作用,因为Comparator <Integer>无法对Collection <Number>进行操作 .

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

  • 136

    简而言之,容易记住PECS

    • 如果需要从集合中检索 T 类型的对象,请使用 <? extends T> 通配符 .

    • 如果需要将 T 类型的对象放入集合中,请使用 <? super T> 通配符 .

    • 如果你需要满足这两个要求,那就不要使用任何通配符 . 尽可能简单 .

  • 20

    (添加一个答案,因为从来没有足够的Generics通配符示例)

    // 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);
            }
        }
    
  • 0

    我们假设这个层次结构:

    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
    

    Let's clarify PE - Producer Extends:

    List<? extends Shark> sharks = new ArrayList<>();
    

    为什么你不能在这个列表中添加扩展“Shark”的对象?喜欢:

    sharks.add(new HammerShark());//will result in compilation error
    

    由于您的列表可以是A,B或C at runtime 类型,因此您无法在其中添加任何类型为A,B或C的对象,因为您最终可能会得到java中不允许的组合 .
    实际上,编译器确实可以在编译时看到你添加一个B:

    sharks.add(new HammerShark());
    

    ...但是无法判断在运行时,您的B将是列表类型的子类型还是超类型 . 在运行时,列表类型可以是A,B,C类型中的任何一种 . 因此,您最终无法在DeadHammerShark列表中添加HammerSkark(超类型) .

    *你会说:"OK, but why can't I add HammerSkark in it since it is the smallest type?" . 答:这是最小的 you 知道 . 购买HammerSkark也可以被其他人扩展,你最终会遇到同样的情况 .

    Let's clarify CS - Consumer Super:

    在同一层次结构中,我们可以尝试这样:

    List<? super Shark> sharks = new ArrayList<>();
    

    can 添加到此列表的内容和原因是什么?

    sharks.add(new Shark());
    sharks.add(new DeadHammerShark());
    sharks.add(new HammerSkark());
    

    您可以添加上述类型的对象,因为鲨鱼(A,B,C)下方的任何内容都将始终是鲨鱼(X,Y,Z)上方的任何子类型 . 容易明白 .

    cannot 在Shark上面添加类型,因为 at runtime 添加对象的类型可以在层次结构中高于列表的声明类型(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,......之上的任何类型 . 编译器可以编译你的赋值语句(看起来是正确的)但是, at runtime s(Animal)的类型可以在层次结构中更低比列表的声明类型(可能是Creature,或更高) . 这是不允许的 .

    To sum up

    We use <? super T> to add objects of types equal or below T in list. 我们无法从中读取 .
    We use <? extends T> to read objects of types equal or below T from list. 我们无法添加元素 .

  • 0

    通配符可以通过三种方式使用: - 上限通配符(?extends Type) .

    • 下限通配符(?超类型) .

    • 无界通配符(?) .
      出于本讨论的目的,将变量视为提供两个函数之一是有帮助的: - 在变量中

    “in”变量向代码提供数据 .
    想象一下带有两个参数的复制方法:
    copy(src,dest)
    src参数提供要复制的数据,因此它是“in”参数 .

    • 输出变量

    “out”变量保存数据以供其他地方使用 . 在复制示例中,
    copy(src,dest)
    dest参数接受数据,因此它是“out”参数 .

    使用extends关键字定义带有上限通配符的“in”变量 .
    使用super关键字定义带有下限通配符的“out”变量 .
    在可以使用Object类中定义的方法访问“in”变量的情况下,使用无界通配符 .
    在代码需要作为“in”和“out”变量访问变量的情况下,不要使用通配符 .

    类NaturalNumber
    {

    私人网络;

    public NaturalNumber(int i)

    }

    类EvenNumber扩展了NaturalNumber
    {

    public EvenNumber(int i)
    {
    超级(ⅰ);
    }
    }

    请考虑以下代码:

    List <EvenNumber> le = new ArrayList <>();
    名单<? extends NaturalNumber> ln = le;
    ln.add(new NaturalNumber(35)); //编译时错误

    您可以添加null .
    你可以调用清除 .
    您可以获取迭代器并调用remove .
    您可以捕获通配符并写入从列表中读取的元素 .

  • 4

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

    • 想象一下货车与列车类似的货运列车 .

    • 如果货物 the same or smaller size 比货车= <? super FreightCarSize> ,您可以 put 货运车中的货物

    • 如果您的仓库中有 enough place (超过货物的大小),您可以 unload 来自货车的货物= <? extends DepotSize>

相关问题