什么是PECS(生产者扩大消费者超级)?
在阅读generics时,我遇到了PECS( Producer extends
和Consumer super
)。
有人可以向我解释如何使用PECS解决extends
和super
之间的混淆?
tl; dr: “PECS”来自collections的观点。 如果您只是从通用集合中提取项目,那么它是一个生产者,您应该使用extends
; 如果你只是填充物品,它是一个消费者,你应该使用super
。 如果你使用相同的集合,你不应该使用extends
或者super
。
假设你有一个方法把参数作为一个集合,但是你希望它比接受一个Collection<Thing>
更加灵活。
案例1:你想通过收集,并与每个项目的事情。
那么这个清单是一个生产者 ,所以你应该使用一个Collection<? extends Thing>
Collection<? extends Thing>
。
推理是一个Collection<? extends Thing>
Collection<? extends Thing>
可以包含Thing
任何子types,因此当您执行操作时,每个元素将performance为Thing
。 (你实际上不能向Collection<? extends Thing>
添加任何Collection<? extends Thing>
,因为你不能在运行时知道集合所持有的Thing
的特定子types。)
情况2:你想添加的东西到collections。
那么这个列表是一个消费者 ,所以你应该使用一个Collection<? super Thing>
Collection<? super Thing>
。
这里的推理是不像Collection<? extends Thing>
Collection<? extends Thing>
, Collection<? super Thing>
Collection<? super Thing>
总是可以持有一Thing
不pipe实际的参数化types是什么。 这里你不在乎列表中已经有的Thing
,只要它能够添加一个Thing
。 这是什么? super Thing
? super Thing
保证。
“计算机科学”背后的原理就是以此命名的
- 协变 – ? 扩展MyClass,
- 逆变 – – ? 超级MyClass和
- Invariance / non-Variance – MyClass
下面的图片应该解释这个概念。
图片提供: Andrey Tyukin
PECS(“ 生产者extends
和 “ super
消费者 ”的缩写)可以用“ 获取和放置原则 ”来解释
获取和放置原则(来自Javagenerics和集合)
它指出,
- 当你只从结构中获取值时,使用扩展通配符
- 当你只将值放入一个结构中时,使用超级通配符
- 并且当你们都得到和放置时不要使用通配符 。
让我们通过例子来理解它:
1.对于扩展通配符(获取值,即生产者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()
方法使用extends
,所有以下调用都是合法的。 如果不使用扩展,前两个调用将不合法。
例外 : 不能将任何东西放入用extends
通配符声明的types中 – 除了属于每个引用types的值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.对于超级通配符(把值,即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()
方法使用super
,所以下面的所有调用都是合法的:如果super不被使用,最后两个调用将不合法。
例外 :你不能从super
types声明的types中取出任何东西 – 除了Object
types的值,它是每个引用types的超types:
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.同时获取和放置时,请勿使用通配符
无论何时您将值放入并从同一结构中获取值, 都不应使用通配符 。
public static double sumCount(Collection<Number> nums, int n) { count(nums, n); return sum(nums); }
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(生产者extends
和消费者super
)
助记符 – >input和输出(即返回types原则)
在Java中,参数和types参数不支持inheritance,如下所示。
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 ie return type is don't care }
genericstypes参数:
class Shape { } class DrawShape{ void draw(ArrayList<Shape> al){ } } class DrawObject{ void draw(ArrayList<Object> al){ } } public class Test { public static void main(String[] args) { ArrayList rawArrayList=new ArrayList(); ArrayList<Object> arrayListObject=new ArrayList<>(); ArrayList<Shape> arrayListShape=new ArrayList<>(); DrawShape drawShape=new DrawShape(); drawShape.draw(rawArrayList); // compiles drawShape.draw(arrayListObject); // Error: the type DrawShape is not applicable for the arguments drawShape.draw(arrayListShape); // compiles DrawObject drawObject = new DrawObject(); drawObject.draw(rawArrayList); // compiles drawObject.draw(arrayListObject); // compiles drawObject.draw(arrayListShape); // Error: the type DrawObject is not applicable for the arguments } }
通配符与通配符解决了这些问题:
注意:通配符?
意味着零次或一次 。
通配符描述了一系列types。 有三种不同的通配符:
- 方差/无方差:
?
还是? extends Object
? extends Object
– 无界通配符。 它代表所有types的家庭。 (同方差/非方差) - 协variables:
? extends T
? extends T
(获取值,即生产者extends
) – 通配符的上限 。 它代表所有types的T
是T
亚型,包括T
型。 必须是一个特定types的祖先。你不能添加元素到集合中,只能读取它们(协方差) - 反差:
? super T
? super T
(Put values,即super
消费者) – 具有下限的通配符。 它代表所有typesT
超types的家族,包括T
型。 必须扩展一个特定的types,你不能读取要收集的元素,只能添加它们。(contra-variance)
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 ie Producer `extends`) * * */ public void testCoVariance(List<? extends Shape> list) { list.add(new Shape()); // Error: is not applicable for the arguments (Shape) ie inheritance is not supporting list.add(new Circle()); // Error: is not applicable for the arguments (Circle) ie inheritance is not supporting list.add(new Square()); // Error: is not applicable for the arguments (Square) ie inheritance is not supporting list.add(new Rectangle()); // Error: is not applicable for the arguments (Rectangle) ie 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 ie Consumer`super`) * */ public void testContraVariance(List<? super Shape> list) { list.add(new Shape());//compiles ie inheritance is supporting list.add(new Circle());//compiles ie inheritance is supporting list.add(new Square());//compiles ie inheritance is supporting list.add(new Rectangle());//compiles ie inheritance is supporting Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer /*You can add a Shape,Circle,Square,Rectangle to a List<? extends Shape> * You can't get an object and don't know what kind of Shape it is. */ } }
Angelika Langer最好学习仿制药
通配符使用指南
学习使用generics进行编程时,更令人困惑的一个方面是确定何时使用上界有界的通配符,何时使用下界有界的通配符。
为了讨论的目的,将variables看作提供以下两个函数之一是有帮助的:
一个“In”variables
一个“in”variables将数据提供给代码。 想象一下有两个参数的复制方法: copy(src, dest)
。 src
参数提供要复制的数据,所以它是“in”参数。
一个“出”variables
“out”variables保存用于其他地方的数据。 在复制示例中, copy(src, dest)
, dest
参数接受数据,所以它是“out”参数。
通配符指南:
- 一个“in”variables用一个上界的通配符来定义,使用
extends
关键字。 - 使用
super
关键字定义一个“out”variables,其下限为通配符。 - 在可以使用
Object
类中定义的方法访问“in”variables的情况下,使用无界通配符。 - 在代码需要以“in”和“out”variables访问variables的情况下,不要使用通配符。
资源
比喻: 来源
协变与马匹与人的对立
在下图中,typesA是一组马,typesB是一组人,箭头描述了Function1 [A,B]types的函数。
方差告诉我,我可以使用这个函数的types比函数1 [A,B]更灵活,我可以使用任何types的Function1 [X,Y],其中X是A的一个子集,Y是B.“在这种types中使用”意味着我在我知道的情况下(或者说,编译器可以certificate)传递给函数的参数始终是集合X的成员,并且代码处理函数的结果可以处理任何属于集合Y的成员的值。如下图所示:
但是Function1 [-A,+ B]的完整types包含这些有趣的小符号,表示方差,减号表示它在A中是逆变的,在B中是正协变的加号意思。
方差告诉我,我可以使用这个函数的types比函数1 [A,B]更灵活,我可以使用任何types的Function1 [X,Y],其中X是A的一个子集,Y是B.“在这种types中使用”意味着我在我知道的情况下(或者说,编译器可以certificate)传递给函数的参数始终是集合X的成员,并且代码处理函数的结果可以处理任何属于集合Y的成员的值。如下图所示:
如果我把function限制在棕色的马上,它一点也不会伤害,它总是会为那匹马返回一个明确定义的人。 但是如果我和一头牛一起打电话就会失败。
正如我在回答另一个问题时解释的那样,PECS是由Josh Bloch创build的助记符,用于帮助记忆产品的使用者。
这意味着当传递给方法的参数化types将产生
T
实例(它们将以某种方式从中检索)? extends T
? extends T
应该被使用,因为T
的子类的任何实例也是T
当传递给方法的参数化types将消耗
T
实例(它们将被传递给它来做某事)? super T
应该使用? super T
因为T
一个实例可以合法地传递给任何接受T
超types的方法。 例如,一个Comparator<Number>
可以在一个Collection<Integer>
使用。? extends T
由于Comparator<Integer>
无法在Collection<Number>
上运行,所以? extends T
将无法工作。
请注意,一般你应该只使用? extends T
? extends T
和? super T
? super T
为某些方法的参数。 方法应该使用T
作为generics返回types的types参数。
简而言之,容易记住PECS
- 使用
<? extends T>
如果需要从集合中检索typesT
对象,则<? extends T>
通配符。 - 使用
<? super T>
<? super T>
通配符,如果你需要把typesT
对象在集合中。 - 如果你需要同时满足这两件事,那么不要使用任何通配符。 尽可能简单。
(添加一个答案,因为没有足够的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); } }