什么是PECS?

PECS:Producer Extends,Consumer Super。泛型的本质是参数化类型

意思是,如果参数化类型表示一个生产者,就使用<? extends T>,用于灵活读取,使得方法可以读取 E 或 E 的任意子类型的容器对象;

如果它表示一个消费者,就使用<? super T>,用于灵活写入,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象。

不明白?往下看。

上界通配符 < ? extends E>

下面是Stack的API接口:

public class Stack<E> extends Vector<E>{
    public Stack();
    public void push(E e);
    public E peek();
    public E pop();
    public boolean empty();
    public int search(Object o);
}

假设想增加一个方法,按顺序将一些元素全部放入Stack中,你可能想到的实现方式如下:

public void pushAll(Iterable<E> src){
    for(E e : src)
        push(e);
}

假设有个Stack,想要灵活的处理Integer,Long等Number的子类型的集合

Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ....;
numberStack.pushAll(integers);

此时代码编译无法通过,因为对于类型Number和Integer来说,虽然Integer是Number的子类,但是对于任意Number集合(如List)不是Integer集合(如List)的父类,因为泛型是不可变的。泛型中没有逻辑上的父子关系,如 List 并不是 List 的父类。两者擦除之后都是List.

java提供了一种叫有限通配符的参数化类型,将pushAll参数替换为“E的某个子类型的Iterable接口”:

public void pushAll(Iterable<? extends E> src){
    for (E e: src)
        push(e);
}

此时可以正确编译了,这里的<? extends E>就是producer-extends。这里的Iterable是生产者,要使用<? extends E>。因为Iterable<? extends E>可以容纳任何E的子类,即?可以是任何E的子类。在执行操作时,可迭代对象的每个元素都可以当作是E来操作。

下界通配符 < ? super E>

与之对应的是:假设有一个方法popAll()方法,从Stack集合中弹出每个元素,添加到指定集合中去。

public void popAll(Collection<E> dst){
  if(!isEmpty()){
    dst.add(pop());
  }
}

假设有一个Stack和Collection对象:

Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ...;
numberStack.popAll(objects);

同样上面这段代码也无法编译通过,解决的办法是使用Collection<? super E>。这里的objects是消费者,因为是添加元素到objects集合中去,objects是一个接受内容的容器。使用Collection<? super E>后,无论objects是什么类型的集合,只要他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。

总结

使用的规则就是:生产者有上限、消费者有下限,即PECS: producer-extends, costumer-super

如果需要遍历collection,并对每一项元素操作时,此时这个集合是生产者(生产元素),应该使用 Collection<? extends E>定义.

如果是添加元素到collection中去,那么此时集合是消费者(消费元素,容纳)应该使用Collection<? super E>.

如果既是生产者又是消费者,那使用参数化类型就没意义了,此时需要的是精确的参数类型。

泛型的通配符

泛型中有三种通配符形式:

<?> 无限制通配符

<? extends E> 上界通配符, extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类

<? super E> 下界通配符, super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

留言

2018-03-10