What is PECS (Producer Extends Consumer Super)?
我在读仿制药时遇到了PEC(生产商
有人能给我解释一下如何使用PEC来解决
tl;dr:"pecs"是从集合的角度来看的。如果您只是从一般集合中提取项目,那么它是一个生产者,您应该使用
假设您有一个方法,它将一组事物作为参数,但您希望它比只接受一个
案例1:你想通过收集来处理每一个项目。然后列表是一个生产者,所以您应该使用
理由是,
案例2:您想向集合中添加内容。那么这个列表就是一个消费者,所以您应该使用
这里的理由是,与
在计算机科学中,这背后的原理被称为
- 协方差:
? extends MyClass , - 反向:
? super MyClass 和 - 不变性/非方差:
MyClass 。
下面的图片应该解释这个概念。
图片提供者:Andrey Tyukin
PECS(生产者
它说,
让我们举例来理解它:
1。用于扩展通配符(获取值,即生产者
这里有一个方法,它获取一组数字,将每个数字转换成一个
1 2 3 4 5 6 | public static double sum(Collection<? extends Number> nums) { double s = 0.0; for (Number num : nums) s += num.doubleValue(); return s; } |
让我们调用这个方法:
1 2 3 4 5 6 |
由于
例外:不能将任何内容放入使用
1 2 3 4 5 6 | 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。对于超级通配符(Put值,即消费者
这里有一种方法,它取一组数字和一个
1 2 3 | public static void count(Collection<? super Integer> ints, int n) { for (int i = 0; i < n; i++) ints.add(i); } |
让我们调用这个方法:
1 2 3 4 5 6 7 8 9 | 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]"); |
由于
例外:不能从用
1 2 3 4 5 |
三。当get和put都使用时,不要使用通配符
无论何时将值放入同一结构或从同一结构中获取值,都不应使用通配符。
1 2 3 4 | public static double sumCount(Collection<Number> nums, int n) { count(nums, n); return sum(nums); } |
PECS(生产者
助记法→取放原理。
该原则规定:
- 仅从结构中获取值时,使用扩展通配符。
- 仅将值放入结构时使用超级通配符。
- 当你得到和得到的时候不要使用通配符。
Java中的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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的子类型,则t类型的对象可以替换为s类型的对象。
在编程语言的类型系统中,输入规则
- 协变的,如果它保持类型的顺序(≤),它将类型从更具体的排序到更一般的排序;
- 相反的,如果它颠倒了这个顺序;
- 如果两者都不适用,则为不变的或非不变的。
协变与逆变
- 只读数据类型(源)可以是协变的;
- 只写数据类型(接收器)可以是反向的。
- 同时充当源和接收器的可变数据类型应该是不变的。
为了说明这一一般现象,请考虑数组类型。对于类型动物,我们可以制作类型动物[]
- 协变:猫是动物;
- 相反:动物是猫;
- 不变式:动物[]不是猫[],猫[]不是动物[]。
Java实例:
1 2 3 4 5 6 7 8 9 10 | 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 通配符。
注:通配符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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. */ } } |
泛型和示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 } } |
正如我在回答另一个问题时所解释的,PECS是乔希·布洛克(Josh Bloch)发明的一种助记装置,用来帮助记忆制片人
This means that when a parameterized type being passed to a method will produce instances of
T (they will be retrieved from it in some way),? extends T should be used, since any instance of a subclass ofT is also aT .When a parameterized type being passed to a method will consume instances of
T (they will be passed to it to do something),? super T should be used because an instance ofT can legally be passed to any method that accepts some supertype ofT . AComparator could be used on aCollection , for example.? extends T would not work, because aComparator could not operate on aCollection .
注意,一般情况下,对于某些方法的参数,应该只使用
简言之,记住PEC的三个简单规则:
(添加一个答案,因为用泛型通配符的例子从来就不够多)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | // 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); } } |
让我们假设这个层次结构:
1 2 3 4 5 6 | 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生产商的扩展:
1 | List<? extends Shark> sharks = new ArrayList<>(); |
为什么不能在此列表中添加扩展"shark"的对象?像:
1 | sharks.add(new HammerShark());//will result in compilation error |
由于在运行时有一个可以是A、B或C的列表,所以不能在其中添加任何类型的A、B或C对象,因为最终可以使用Java中不允许的组合。实际上,编译器在compileTime中确实可以看到添加了b:
1 | sharks.add(new HammerShark()); |
…但它无法判断在运行时,您的B是否是列表类型的子类型或父类型。在运行时,列表类型可以是A、B、C类型中的任何一种。因此,您不能以在Deadhammershark列表中添加Hammerskark(超级类型)为例。
*你会说:"好吧,但是为什么我不能在里面加上Hammerskark,因为它是最小的类型?".答:它是你所知道的最小的。购买Hammerskark也可以由其他人扩展,最终你也会遇到同样的情况。
让我们澄清一下CS-Consumer Super:
在相同的层次结构中,我们可以尝试:
1 | List<? super Shark> sharks = new ArrayList<>(); |
什么以及为什么可以添加到此列表中?
1 2 3 | sharks.add(new Shark()); sharks.add(new DeadHammerShark()); sharks.add(new HammerSkark()); |
您可以添加上述类型的对象,因为Shark(A、B、C)以下的任何对象都将始终是Shark(X、Y、Z)以上的任何对象的子类型。容易理解。
不能在shark之上添加类型,因为在运行时,添加的对象的类型在层次结构中可以高于声明的列表类型(x、y、z)。这是不允许的。
但是为什么你不能从这个列表中读?(我的意思是,您可以从中获取元素,但不能将其分配给对象o以外的任何其他对象):
1 2 3 4 5 |
在运行时,列表类型可以是a:x、y、z……之上的任何类型。编译器可以编译您的赋值语句(看起来是正确的),但是,在运行时,s(animal)的类型在层次结构上可能比声明的列表类型(可能是生物或更高)低。这是不允许的。
总结
我们使用
记住这一点:
Consumer eat supper(super); Producer extends his parent's factory
协方差:接受子类型反向:接受父类型
协变类型是只读的,而逆变类型是只读的。
使用现实生活中的例子(经过一些简化):