泛型是在Java 1.5中被加入了,這里不討論泛型的細節問題,這個在Thinking in Java第四版中講的非常清楚,這里要講的是super和extends關鍵字,以及在使用這兩個關鍵字的時候為什么會不同的限制。
首先,我們定義兩個類,A和B,並且假設B繼承自A。
package com.wms.test; import java.util.ArrayList; import java.util.List; public class Generic { public static void main(String[] args){ List<? extends A> list1 = new ArrayList<A>(); // list1.add(new A()); //錯誤,編譯器無法確定List所持有的類型,所以無法安全的向其中添加對象 A a = list1.get(0); List<? extends A> list2 = new ArrayList<B>(); List<? super B> list3 = new ArrayList<B>(); list3.add(new B()); //想要正確,必須向下轉型,但是向下轉型是不安全的,非常容易出錯 // B b = list3.get(0); //編譯器無法確定get返回的對象類型是B,還是B的父類或 Object. List<? super B> list4 = new ArrayList<A>(); } } class A{} class B extends A{}
從上面這段創建List的代碼我們就更加容易理解super和extends關鍵字的含義了。首先要說明的一點是,Java強制在創建對象的時候必須給類型參數制定具體的類型,不能使用通配符,也就是說new ArrayList<? extends A>(),new ArrayList<?>()這種形式的初始化語句是不允許的。
從上面main函數的第一行和第二行,我們可以理解extends的含義,在創建ArrayList的時候,我們可以指定A或者B作為具體的類型。也就是,如果<? extends X>,那么在創建實例的時候,我們就可以用X或者擴展自X的類為泛型參數來作為具體的類型,也可以理解為給"?"號指定具體類型,這就是extends 的含義。
同樣的,第三行和第四行就說明,如果<? super X>,那么在創建實例的時候,我們可以指定X或者X的任何的超類來作為泛型參數的具體類型。
當我們使用List<? extends X>這種形式的時候,調用List的add方法會導致編譯失敗,因為我們在創建具體實例的時候,可能是使用了X也可能使用了X的子類,而這個信息編譯器是沒有辦法知道的,同時,對於ArrayList<T>來說,只能放一種類型的對象。這就是問題的本質。而對於get方法來說,由於我們是通過X或者X的子類來創建實例的,而用超類來引用子類在Java中是合法的,所以,通過get方法能夠拿到一個X類型的引用,當然這個引用可以指向X也可以指向X的任何子類。
而當我們使用List<? super X>這種形式的時候,調用List的get方法會失敗。因為我們在創建實例的時候,可能用了X也可能是X的某一個超類,那么當調用get的時候,編譯器是無法准確知曉的。而調用add方法正好相反,由於我們使用X或者X的超類來創建的實例,那么向這個List中加入X或者X的子類肯定是沒有問題的, 因為超類的引用是可以指向子類的。
最后還有一點,這兩個關鍵字的出現都是因為Java中的泛型沒有協變特性的導致的。
小結(此處還是沒太明白,有待解決)
extends 可用於的返回類型限定,不能用於參數類型限定。 super 可用於參數類型限定,不能用於返回類型限定。 >帶有super超類型限定的通配符可以向泛型對象中寫入,帶有extends子類型限定的通配符可以向泛型對象讀取。
什么是PECS?
PECS指“Producer Extends,Consumer Super”。換句話說,如果參數化類型表示一個生產者,就使用<? extends T>;如果它表示一個消費者,就使用<? super T>,可能你還不明白,不過沒關系,接着往下看好了。
下面是一個簡單的Stack的API接口:
public interface Stack<E>{ public Stack(); public void push(E e): public E pop(); public boolean isEmpty(); }
假設想增加一個方法,按順序將一系列元素全部放入Stack中,你可能想到的實現方式如下:
public void pushAll(Iterable<E> iter) { for(E e : iter) push(e); }
假設有個Stack<Number>,想要靈活的處理Integer,Long等Number的子類型的集合
Stack<Number> stack = new Stack<Number>(); Iterable<Integer> iter = null; /* * 此時代碼編譯無法通過,因為對於類型Number和Integer來說,雖然后者是Number的子類, * 但是對於任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超類, * 因為泛型是不可變的。 */ stack.pushAll(iter); //錯誤
此時代碼編譯無法通過,因為對於類型Number和Integer來說,雖然后者是Number的子類,但是對於任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超類,因為泛型是不可變的。
幸好java提供了一種叫有限通配符的參數化類型,pushAll參數替換為“E的某個子類型的Iterable接口”:
public void pushAll01(Iterable<? extends E> iter) { for(E e : iter) push(e); }
此時再這樣調用:
/* * 這樣就可以正確編譯了,這里的<? extends E>就是所謂的 producer-extends。 * 這里的Iterable就是生產者,要使用<? extends E>。 * 因為Iterable<? extends E>可以容納任何E的子類。在執行操作時,可迭代對象的每個元素都可以當作是E來操作。 */ stack.pushAll01(iter); //正確
這樣就可以正確編譯了,這里的<? extends E>就是所謂的 producer-extends。這里的Iterable就是生產者,要使用<? extends E>。因為Iterable<? extends E>可以容納任何E的子類。在執行操作時,可迭代對象的每個元素都可以當作是E來操作。
與之對應的是:假設有一個方法popAll()方法,從Stack集合中彈出每個元素,添加到指定集合中去。
public void popAll(Collection<E> c) { c.add(pop()); }
假設有一個Stack<Number>和Collection<Object>對象:
Stack<Number> stack = new Stack<Number>(); Collection<Object> c = null; /* * 該方法要正確,必須c為Collection<Number>,和上面同理 */ stack.popAll(c); //錯誤
同樣上面這段代碼也無法通過,解決的辦法就是使用Collection<? super E>。這里的objects是消費者,因為是添加元素到objects集合中去。使用Collection<? super E>后,無論objects是什么類型的集合,滿足一點的是他是E的超類,所以不管這個參數化類型具體是什么類型都能將E裝進objects集合中去。
public void popAll01(Collection<? super E> c) { c.add(pop()); }
/* * 同樣上面這段代碼也無法通過,解決的辦法就是使用Collection<? super E>。 * 這里的objects是消費者,因為是添加元素到objects集合中去。 * 使用Collection<? super E>后,無論objects是什么類型的集合, * 滿足一點的是他是E的超類,所以不管這個參數化類型具體是什么類型都能將E裝進objects集合中去。 */ stack.popAll01(c);
綜合代碼:
package com.wms.test; import java.util.Collection; public class Generic { public static void main(String[] args) { Stack<Number> stack = new Stack<Number>(); Iterable<Integer> iter = null; /* * 此時代碼編譯無法通過,因為對於類型Number和Integer來說,雖然后者是Number的子類, * 但是對於任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超類, * 因為泛型是不可變的。 */ stack.pushAll(iter); //錯誤 /* * 這樣就可以正確編譯了,這里的<? extends E>就是所謂的 producer-extends。 * 這里的Iterable就是生產者,要使用<? extends E>。 * 因為Iterable<? extends E>可以容納任何E的子類。在執行操作時,可迭代對象的每個元素都可以當作是E來操作。 */ stack.pushAll01(iter); //正確 Collection<Object> c = null; /* * 該方法要正確,必須c為Collection<Number>,和上面同理 */ stack.popAll(c); //錯誤 /* * 同樣上面這段代碼也無法通過,解決的辦法就是使用Collection<? super E>。 * 這里的objects是消費者,因為是添加元素到objects集合中去。 * 使用Collection<? super E>后,無論objects是什么類型的集合, * 滿足一點的是他是E的超類,所以不管這個參數化類型具體是什么類型都能將E裝進objects集合中去。 */ stack.popAll01(c); } } class Stack<E> { public Stack(){ } public void push(E e) { } public void pushAll(Iterable<E> iter) { for(E e : iter) push(e); } public void pushAll01(Iterable<? extends E> iter) { for(E e : iter) push(e); } public E pop() { return null; } public void popAll(Collection<E> c) { c.add(pop()); } public void popAll01(Collection<? super E> c) { c.add(pop()); } }