Java泛型 PECS(Producer Extends, Consumer Super)

了解PECS前需要了解 java继承、泛型擦除、里氏替换原则。

对泛型的超类和子类进行泛化。一般泛化只能对同类型的进行,所以ide进行类型擦除时会报错。引入PECS。

PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>,可能你还不明白,不过没关系,接着往下看好了。

下面是一个简单的Stack的API接口:

复制代码
public class Stack {
public Stack();
public void push(E e):
public E pop();
public boolean isEmpty();
}
复制代码
假设想增加一个方法,按顺序将一系列元素全部放入Stack中,你可能想到的实现方式如下:

public void pushAll(Iterable src) {
for(E e : src)
push(e)
}
假设有个Stack,想要灵活的处理Integer,Long等Number的子类型的集合

Stack numberStack = new Stack();
Iterable integers = …;
numberStack.pushAll(integers);
此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,但是对于任意Number集合(如List)不是Integer集合(如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来操作。

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

public void popAll(Collection dst){
if(!isEmpty()){
dst.add(pop());
}
}
假设有一个Stack和Collection对象:

Stack numberStack = new Stack();
Collection objects = …;
numberStack.popAll(objects);

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

总结
如果你是想遍历collection,并对每一项元素操作时,此时这个集合时生产者(生产元素),应该使用 Collection<? extends Thing>;

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

在类型里面要定义具体的泛型不能出现;

<?extendsNumber> 这种只能这样使用,因为?的使用是通配符是要取替E这种协定的泛型: List<? extends Fruit> fruits = new ArrayList<>(); 在方法上这样使用: public R exec(ProcessParam request, Class resultClass, String paramKey) 通俗易懂:https://www.zhihu.com/question/20400700
上一篇:Python之迭代器


下一篇:python中combinations 的用法