关于PECS原则

Producer Extends Consumer Super

Posted by LiuShuo on April 6, 2020

关于Java的泛型,我们经常使用<? super T>和<? extends T> 两种方式进行变量声明,这里面涉及一个叫PECS的概念,如果理解不到位 可能会造成对集合进行set或get的时候编译报错,让你一头雾水。

概念

PECS的意思是Producer Extends Consumer Super,简单理解为如果是生产者则使用Extends,如果是消费者则使用Super,不过,这到底是啥意思呢?

下面的解释来自StackOverflow,很精辟:

tl;dr: “PECS” is from the collection’s point of view. If you are only pulling items from a generic collection, it is a producer and you should use extends; if you are only stuffing items in, it is a consumer and you should use super. If you do both with the same collection, you shouldn’t use either extends or super.

让我们通过一个典型的例子理解一下到底什么是Producer和Consumer:

1
2
3
4
5
6
7
public class Collections { 
  public static <T> void copy(List<? super T> dest, List<? extends T> src)   {  
      for (int i=0; i<src.size(); i++) { 
          dest.set(i, src.get(i)); 
      }
  } 
}

上面的例子中将src中的数据复制到dest中,这里src就是生产者,它「生产」数据,dest是消费者,它「消费」数据。设计这样的方法,好处就是,可以复制任意类型的List,通用性特别强, 不用使用Object来进行强制转换。

1
2
3
Object做为参数完全可以替代泛型,但是使用Object做为参数,就可以用任何类型,不能限定类型。
使用泛型,可以帮助开发人员在编译的时候就能定位错误。
而使用Object,只能在运行的时候才能发现错误。

举个例子

举一个水果相关的例子:

  • List<? extends Fruit>的泛型集合中,对于元素的类型,编译器只能知道元素是继承自Fruit,具体是Fruit的哪个子类是无法知道的。 所以「向一个无法知道具体类型的泛型集合中插入元素是不能通过编译的」。但是由于知道元素是继承自Fruit,所以从这个泛型集合中取Fruit类型的元素是可以的。

  • List<? super Apple>的泛型集合中,元素的类型是Apple的父类,但无法知道是哪个具体的父类,因此「读取元素时无法确定以哪个父类进行读取」。 插入元素时可以插入Apple与Apple的子类,因为这个集合中的元素都是Apple的父类,子类型是可以赋值给父类型的。

来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Fruit {}

public class Apple extends Fruit {}

public class AppleLite extends Apple {}

public void testCoVariance(List<? extends Apple> apples) {
    Apple b = new Apple();
    AppleLite c = new AppleLite();
    apples.add(b); // does not compile
    apples.add(c); // does not compile
    Fruit a = myBlist.get(0);
}

public void testContraVariance(List<? super Apple> apples) {
    Apple b = new Apple();
    AppleLite c = new AppleLite();
    apples.add(b);
    apples.add(c);
    Fruit a = apples.get(0); // does not compile
}

发现上面凡是? extends T的集合,也就是Producer都不能「写」,凡是? super T的集合,也就是Consumer都不能「读」。

有一个比较好记的口诀:

  • 只读不可写时,使用List<? extends Fruit>:Producer
  • 只写不可读时,使用List<? super Apple>:Consumer

A producer is allowed to produce something more specific, hence extends, a consumer is allowed to accept something more general, hence super.

References

本文首次发布于 LiuShuo’s Blog, 转载请保留原文链接.