我遇到了pecs(缩短了 制作人 extends 和消费者 super)阅读仿制药。

有人可以向我解释如何使用PEC解决之间的混乱 extendssuper?

有帮助吗?

解决方案

tl; dr: “ PECS”是从该系列的角度来看的。如果你是 只要 从通用系列中汲取物品,它是生产者,您应该使用 extends;如果你是 只要 填充物品,这是一个消费者,您应该使用 super. 。如果您同时使用同一集合,则不应该使用 extends 或者 super.


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

案例1:您想浏览该集合并使用每个项目做事。
然后列表是 制作人, ,所以你应该使用 Collection<? extends Thing>.

原因是 Collection<? extends Thing> 可以保持任何子类型 Thing, ,因此每个元素都会表现为 Thing 执行操作时。 (您实际上无法将任何内容添加到 Collection<? extends Thing>, ,因为您在运行时不知道哪个 具体的 子类型 Thing 该系列成立。)

案例2:您想将内容添加到集合中。
然后列表是 消费者, ,所以你应该使用 Collection<? super Thing>.

这里的原因是与 Collection<? extends Thing>, Collection<? super Thing> 总是可以持有 Thing 无论实际参数化类型是什么。在这里,您不在乎列表中的内容,只要它允许 Thing 要添加;这是什么 ? super Thing 保证。

其他提示

计算机科学中的原则称为

  • 协方差: ? extends MyClass,
  • 违反: ? super MyClass
  • 不变性/不变性: MyClass

下图应解释该概念。图片提供: 安德烈·泰金(Andrey Tyukin)

Covariance vs Contravariance

PECS(生产者 extends 和消费者 super)

mnemonic→获取并放置原理。

该原则指出:

  • 当您仅从结构中获得值时,请使用扩展通配符。
  • 当您仅将值放入结构中时,请使用超级通配符。
  • 而且,当你们俩得到并放置时,不要使用通配符。

Java中的示例:

class Super {

    Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
    void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}

class Sub extends Super {

    @Override
    String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object) 
    @Override
    void testContraVariance(String parameter){} //doesn't support even though String is subtype of Object

}

Liskov替代原则: 如果S是T的亚型,则可以用S类型的对象代替T型的对象。

在编程语言的类型系统中,打字规则

  • 协变 如果它保留了类型(≤)的排序,该订单从更特定到更通用的类型订购;
  • 违反 如果逆转此顺序;
  • 不变 或非变化,如果这些都不适用。

协方差和违反

  • 只读数据类型(来源)可以是 协变;
  • 仅写入数据类型(接收器)可以是 违反.
  • 可变的数据类型应充当来源和水槽 不变.

为了说明这种一般现象,请考虑阵列类型。对于类型的动物,我们可以使动物类型[

  • 协变: :猫[]是动物[];
  • 违反: :动物[]是猫[];
  • 不变: :动物[]不是猫[],而猫[]不是动物[]。

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  

更多示例

有限(即前往某个地方) 通配符 :有3种不同的通配符:

  • 内变/非变异: ? 或者 ? extends Object - 无限 通配符。它代表各种类型的家庭。当你们俩得到并放置时使用。
  • 共同变异: ? extends T (所有类型的家族都是亚型 T) - 一个通配符 上限. T 是个 - 继承层次结构中的大多数类。使用 extends 只有你只有你的通配符 得到 从结构中的值。
  • 反对变异: ? super T (所有类型的家族都是超级型 T) - 一个带有一个的通配符 下限. T 是个 降低- 继承层次结构中的大多数类。用一个 super 只有你只有你的通配符 值成一个结构。

注意:通配符 ? 方法 零或一次, ,代表未知类型。通配符可以用作参数的类型,从不用作通用方法调用的类型参数,一个通用类实例创建。 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 Test {
 /*
   * 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<? super Shape> 
        * You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
        */  
    }
}

仿制药例子

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
    }
}

正如我在 我的答案 在另一个问题上,PEC是由Josh Bloch创建的助记符设备,以帮助记住 pRoducer extends, C鼻子 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 作为通用返回类型上的类型参数。

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

  1. 使用 <? extends T> 通配符如果您需要检索类型的对象 T 从系列。
  2. 使用 <? super T> 通配符如果您需要放置类型的对象 T 在收藏中。
  3. 如果您需要满足这两件事,那么,不要使用任何通配符。就如此容易。

(添加答案是因为仿制药通配符的示例永远不会足够)

       // 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

让我们澄清PE-生产者扩展:

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

为什么您不能在此列表中添加扩展“鲨鱼”的对象?像:

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

由于您的列表可以是A型,B或C类 在运行时, ,您不能在其中添加任何A类,B或C的对象,因为您可以最终得到Java中不允许的组合。
实际上,编译器确实可以在编译时看到您添加B:

sharks.add(new HammerShark());

...但是它无法确定在运行时,您的B是否将是列表类型的子类型或超模型。在运行时,列表类型可以是任何类型A,B,C。因此,您最终不能在DeadHammerShark列表中添加Hammerskark(Super Type)。

*您会说:“好,但是为什么我不能在其中添加Hammerskark,因为它是最小的类型?”。答:这是最小的 知道。但是Hammerskark也可以被别人扩展,您最终会处于同一情况下。

让我们澄清CS-消费者超级:

在同一层次结构中,我们可以尝试以下方法:

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

你为什么以及为什么 能够 添加到此列表?

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

您可以添加上述对象类型,因为鲨鱼(a,b,c)下方的任何物体始终是鲨鱼上方的任何东西(x,y,z)的亚型。容易明白。

不能 在鲨鱼上方添加类型,因为 在运行时 层次结构中添加的对象的类型可能比列表的声明类型(X,Y,Z)高。这是不允许的。

但是,为什么您无法从此列表中阅读? (我的意思是您可以从中获得一个元素,但是您不能将其分配给对象o以外的任何内容):

Object o;
o = sharks.get(2);// only assignment that works

Animal s;
s = sharks.get(2);//doen't work

在运行时,列表的类型可以是上方的任何类型:x,y,z,...编译器可以编译您的分配语句(看起来正确),但是,,但是, 在运行时 S(动物)的类型在层次结构中可能低于列表的声明类型(可能是生物或更高的列表)。这是不允许的。

总结一下

我们用 <? super T> 在列表中添加等于或以下的类型的对象。 我们无法从中读取。
我们用 <? extends T> 读取与列表相等或以下类型的对象。 我们不能添加元素。

记住这一点:

消费者吃 晚餐(极好的);制作人 扩展 他父母的工厂

协方差: :接受子类型
逆向: :接受超级型

协变量类型仅读取,而违反类型仅写入。

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

  1. 想象一辆带货车的货运火车与清单类似。
  2. 你可以 如果货物有货车中的货物 相同或较小的尺寸 比货车= <? super FreightCarSize>
  3. 你可以 卸下 如果有的话,货车的货物 足够的地方 (超过货物的大小)在您的仓库中= <? extends DepotSize>
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top