PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>。
不明白?
先看看<? extends T>和<? super T>的区别。
<? extends T>
和<? super T>
是Java泛型中的“通配符(Wildcards)”和“边界(Bounds)”的概念。
-
<? extends T>
:是指 “上界通配符(Upper Bounds Wildcards)” -
<? super T>
:是指 “下界通配符(Lower Bounds Wildcards)”
假设有这样一些类型定义
//Lev 1 class Food{} //Lev 2 class Fruit extends Food{} class Meat extends Food{} //Lev 3 class Apple extends Fruit{} class Banana extends Fruit{} class Pork extends Meat{} class Beef extends Meat{} //Lev 4 class RedApple extends Apple{} class GreenApple extends Apple{}
定义一个容器,Plate类。盘子里可以放一个泛型的“东西”:
class Plate<T>{ private T item; public Plate(T t){item=t;} public void set(T t){item=t;} public T get(){return item;} }
现在我定义一个“水果盘子”,逻辑上水果盘子当然可以装苹果。
Plate<Fruit> p=new Plate<Apple>(new Apple());
但实际上Java编译器不允许这个操作。会报错,“装苹果的盘子”无法转换成“装水果的盘子”。
其实很好解释:
- 苹果 IS-A 水果
- 装苹果的盘子 NOT-IS-A 装水果的盘子
所以,就算容器里装的东西之间有继承关系,但容器之间是没有继承关系的。于是有了<? extends T>
和<? super T>。
上界通配符 Plate<? extends Fruit>
覆盖下图中蓝色的区域。
下界通配符 Plate<? super Fruit>
覆盖下图中红色的区域。
接下来看一下PECS,再举个例子:
下面是一个简单的Stack的API接口:
public class Stack<E>{ public Stack(); public void push(E e): public E pop(); public boolean isEmpty(); //按顺序将一系列元素全部放入Stack中,你可能想到的实现方式如下: public void pushAll(Iterable<E> src){ for(E e : src) push(e) } }
这时,有个Stack<Number>,想要灵活的处理Integer,Long等Number的子类型的集合:
Stack<Number> numberStack = new Stack<Number>(); Iterable<Integer> integers = ....; numberStack.pushAll(integers);
此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,但是对于任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超类,因为泛型是不可变的。
幸好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<E> dst){ if(!isEmpty()){ dst.add(pop()); } }
假设有一个Stack<Number>和Collection<Object>对象:
Stack<Number> numberStack = new Stack<Number>(); Collection<Object> objects = ...; numberStack.popAll(objects);
同样上面这段代码也无法通过,解决的办法就是使用Collection<? super E>。这里的objects是消费者,因为是添加元素到objects集合中去。使用Collection<? super E>后,无论objects是什么类型的集合,满足一点的是他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。
btw,我们反过来呢?以下两种均不能通过编译。
public void pushAll(Iterable<? super E> src){ for (E e: src) push(e); }
因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是E的基类,那往里存粒度比E小的都可以。但往外读取元素就费劲了,只有所有类的基类Object对象才能装下。但这样的话,元素的类型信息就全部丢失。Stack自然也无法存E的基类。
public void popAll(Collection<? extends E> dst){ if(!isEmpty()){ dst.add(pop()); } }
原因是编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道。编译器在?标上一个占位符:CAP#1,来表示捕获一个E或E的子类,具体是什么类不知道,代号CAP#1。然后无论是想往里插入A或者B或者C编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。
This means that when a parameterized type being passed to a method will produce instances of T
(they will be retrieved from it in some way), ? extends T
should be used, since any instance of a subclass of T
is also a T
.
When a parameterized type being passed to a method will consume instances of T
(they will be passed to it to do something), ? super T
should be used because an instance of T
can legally be passed to any method that accepts some supertype of T
. A Comparator<Number>
could be used on a Collection<Integer>
, for example. ? extends T
would not work, because a Comparator<Integer>
could not operate on a Collection<Number>
.
--from * https://*.com/questions/2723397/what-is-pecs-producer-extends-consumer-super
另附阿里java开发手册第一章第五节第6条
6.【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用add方 法,而<? super T>不能使用get方法,作为接口调用赋值时易出错。 说明:扩展说一下PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内 容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>。
https://blog.csdn.net/rj08zhou/article/details/45063451