泛型-PECS原則


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://itimetraveler.github.io/2016/12/27/%E3%80%90Java%E3%80%91%E6%B3%9B%E5%9E%8B%E4%B8%AD%20extends%20%E5%92%8C%20super%20%E7%9A%84%E5%8C%BA%E5%88%AB%EF%BC%9F/

https://blog.csdn.net/rj08zhou/article/details/45063451

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM