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 stackOverFlow https://stackoverflow.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