Java中的PECS

最近在看Guava源码的过程中发现了通用类型的用法. 通用类型是JAVA5以后引入的新的功能,用处是当定义一个方法或者一个类,但是不太确定需要传入的参数的类型时,这时候使用通用类型。例子如Java中的List,Map
通用类型中有一个通配符?,在读代码的过程中发现了有? extends, ? super的用法,出于好奇,对这个用法进行了一些多一点的研究。发现了一个原则就是:PECS(Producer extends, consumer super)。

一、什么是PECS?

PECS这个概念是从集合类的概念来讲的。

生产者和消费者
首先,解释下生产者消费者的概念。
举个栗子:有一个List<ExampleType> producer, 如果如果我们只从这个producerList中读取数据,而不从中添加数据,那这个List就称为Producer,即生产者。
同理,如果我们只向一个List<ExampleType> consumer中添加数据,则这样的List就被称为Consumer,即消费者。

生产者使用extends,消费者使用super
这句话的意思是,当使用?定义一个通用类型的时候如果设计的类是生产者,则应该使用extends关键字为此类型指定一个最高父类,如果设计的类是消费者,那应该用super关键字为此类型指定一个最低子类。
是不是听起来还是有点绕?恩,那下面详细聊一聊,生产者和消费者。

二、生产者(? extends)的用法。

假设,现在有以下三种List

1
2
3
List<? extends Number> numList = new ArrayList<Number>(); 
List<? extends Number> intList = new ArrayList<Integer>();
List<? extends Number> doubleList = new ArrayList<Double>();

  1. <? extends Number>最高父类是Number类型
    可以看到,上面三个List在定义的时候都定义成了? extends Number的类型,而Integer, Double都属于Number的子类,所以在实例化List<? extends Number>这样的List时,可以传入任意Number类型的子类以及它自己,进行实例化。
    但是,不能用Number的父类或者与Number没有继承关系的类进行初始化。例如
    1
    2
    List<? extends Number> objList = new ArrayList<Object>(); 
    List<? extends Number> strList = new ArrayList<String>();

也就是说Number类型是? extends Number代表的类型的最高父类。

  1. List<? extends Number>类型的读操作
    由于? extends Number的最高父类是Number类型,所以,从List<? extends Number>中拿出的数据可以保证是Number类型。
    但是由于List<? extends Number>中即可以放Integer类型的实例,又可以放Double类型的实例,所以从List<? extends Number>中拿出的数据不能保证是Integer或者Double类型。
  2. List<? extends Number>类型的写操作
    不能向List<? extends Number>中写入任何数据。
    为什么?假设Java允许向List<? extends Number>中写入数据,会发生什么?
    考虑下面的例子:
    1
    2
    Integer intValue = new Integer(1);
    doubleList.add(inValue);

向一个doublieList中添加一个Integer类型的值,能添加成功吗?答案是可以的,? extends Number只检查List中的元素是否是Number类型,Integer是Number类型,所有添加操作没问题。
问题是doubleList本意是一个Double类型组成的List,现在里面突然有了一个Integer类型的东西闯了进来,这是什么鬼!
所以Java不允许这样的操作。

三、消费者(? super)的用法

假设,现在有以下三种List

1
2
3
List<? super Integer> intList = new ArrayList<Integer>(); 
List<? super Integer> numberList = new ArrayList<Number>();
List<? super Integer> objList = new ArrayList<Object>();

  1. <? super Integer>类型的最小子类是Integer
    <? super Integer>中的super代表了?可以是任意一种Integer的父类类型,所以可以看到上面三个list分别实例化为了IntegerNumberObject类型。
    但是<? super Integer>不能被实例化为Integer的子类,虽然Integer也没有子类。
  2. List<? super Integer>的读操作
    由于<? super Integer>仅仅规定了?的最小子类,没有限制最大父类,所以从List<? super Integer>中读取的数据只能保证是最最大的父类Object。然而,这并没有什么卵用,我们一般写程序的时候很少会直接用Object类型去处理什么业务。
    所以可以从List<? super Integer>读数据,然而读出来的数据并没有什么卵用。
  3. List<? super Integer>的写操作
    好消息!!!写操作是List<? super Integer>的专长。但是,只能向其添加Integer类型或其子类类型。
    为什么?
    因为? super Integer中Integer是其最小子类,也就是最低下限,而Java是一门有下限的语言,有节操,要保证下限。
    好吧,上面是在扯淡。下面正经聊。
    假设Java允许List<? super Integer>添加Integer的父类元素,会发生什么呢?
    举个栗子:
    1
    2
    3
    List<? super Integer> intList = new ArrayList<Integer>(); 
    Object obj = new Object();
    intList.add(obj);

上面intList本意是实例化一个Integer类型的List,然而由于List<? super Integer>允许插入Integer的父类,在一个Integer类型的List中突然闯入了一个Object类型的东东,就像男生突然闯进了女厕所一样。
然而,男生是不能闯进女厕所的,所以Object类型的数据不能插入到Integer类型的List中,所以Java只允许List<? super Integer>中添加Integer类型或其子类。

四、PESC应用实例

说了大半天,PESC有啥卵用啊?少废话,看代码:

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

上面代码中copy方法的功能是将src中的数据复制到dest中,这里src就是生产者,dest就是消费者。
设计这样的方法,好处就是,可以复制任意类型的List,通用性特别强。

参考资料:
java-generics-what-is-pecs
difference-between-super-t-and-extends-t-in-java
Java Generics FAQs - Type Arguments