什么是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不被使用,最后两个调用将不合法。 
  例外 :你不能从supertypes声明的types中取出任何东西 – 除了Objecttypes的值,它是每个引用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); } }