Java 泛型: 什麼是PECS(Producer Extends, Consumer Super)

什麼是PECS? java

PECS指「Producer Extends,Consumer Super」。換句話說,若是參數化類型表示一個生產者,就使用<? extends T>;若是它表示一個消費者,就使用<? super T>,可能你還不明白,不過不要緊,接着往下看好了。spa

下面是一個簡單的Stack的API接口:code

1
2
3
4
5
6
public     class      Stack<E>{
         public     Stack();
         public     void     push(E e):
         public     E pop();
         public     boolean     isEmpty();
}

假設想增長一個方法,按順序將一系列元素所有放入Stack中,你可能想到的實現方式以下:對象

1
2
3
4
public     void     pushAll(Iterable<E> src){
         for     (E e : src)
             push(e)
}

假設有個Stack<Number>,想要靈活的處理Integer,Long等Number的子類型的集合接口

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

此時代碼編譯沒法經過,由於對於類型Number和Integer來講,雖而後者是Number的子類,可是對於任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超類,由於泛型是不可變的。ci

幸虧java提供了一種叫有限通配符的參數化類型,pushAll參數替換爲「E的某個子類型的Iterable接口」:get

1
2
3
4
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來操做。it

與之對應的是:假設有一個方法popAll()方法,從Stack集合中彈出每一個元素,添加到指定集合中去。io

1
2
3
4
5
public     void     popAll(Collection<E> dst){
            if     (!isEmpty()){
                     dst.add(pop());
             }
}

假設有一個Stack<Number>和Collection<Object>對象:編譯

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

一樣上面這段代碼也沒法經過,解決的辦法就是使用Collection<? super E>。這裏的objects是消費者,由於是添加元素到objects集合中去。使用Collection<? super E>後,不管objects是什麼類型的集合,知足一點的是他是E的超類,因此無論這個參數化類型具體是什麼類型都能將E裝進objects集合中去。

總結:

  1. 若是你是想遍歷collection,並對每一項元素操做時,此時這個集合時生產者(生產元素),應該使用 Collection<? extends Thing>.

  2. 若是你是想添加元素到collection中去,那麼此時集合時消費者(消費元素)應該使用Collection<? super Thing>

注:此文根據《Effective Java》以及Java Generics: What is PECS? 整理成文。想了解更多有關泛型相關知識,請讀者閱讀《Effective Java》的第五章。

相關文章
相關標籤/搜索