java泛型學習和實踐(4),java泛
引言
前三節講述了泛型常見聲明及使用,泛型既可以在類上進行聲明,也可以在單個方法上進行聲明,並分別對這兩種情況進行了總結。下面來學習下泛型擴充知識。
延用前面的Runnable介面、Buick類、Ford類、Driver類,新增加一個汽車容器類CarContainer
第一版
代碼如下:
public interface Runnable { public void run();}
public class Buick implements Runnable { @Override public void run(){ System.out.println("buick run"); } public void autoRun(){ System.out.println("buick auto-run"); }}
public class Ford implements Runnable { @Override public void run(){ System.out.println("ford run"); } public void fly(){ System.out.println("ford fly"); }}
<pre name="code" class="java">public class Driver<T extends Runnable> { private T car; public void drive(T car){ this.car = car; System.out.println("I am driving a " + car); car.run(); } public T getDrivingCar(){ return car; }}
public class CarContainer<E extends Runnable> { private List<E> container = new ArrayList<E>(); public void add(E e) { container.add(e); } public void add(Driver<E> producer) { container.add(producer.getDrivingCar()); } public static void main(String[] args) { CarContainer<Runnable> container = new CarContainer<Runnable>(); Buick buick = new Buick(); container.add(buick); Driver<Runnable> driver = new Driver<Runnable>(); driver.drive(buick); container.add(driver); }}
前三個類不多說了,不明白的請參照前三節。說下CarContainer類, add方法接收泛型E類型對象,向容器中新增汽車。另一個重載add方法接收Driver<E>參數,擷取到drivingCar並增加到容器中。
main靜態方法中分別示範了這兩個add的使用方法。先看第一個add方法使用,由於Buick類實現了Runnable介面的,container.add(buick)這樣調用是沒問題的;再看第二個add方法使用:首先driver.drive(buick)也沒問題,原理同上;然後看container.add(driver),由於container的元素類型為Runnable介面,而driver的元素類型也為Runnable介面,類型是完全符合的,因此運行下程式也沒問題,看上去都很美好。
假如有一個Driver<Buick>呢,請大家思考下這樣調用看行不行
Driver<Buick> driver = new Driver<Buick>(); driver.drive(buick); container.add(driver);
結果是不行,編譯時間提示如下錯誤:
The method add(Runnable) in the type CarContainer<Runnable> is not applicable for the arguments (Driver<Buick>)
從邏輯上說,這樣調用應該是可以的,因為Buick實現了Runnable介面,但實際上不行。還好,有一種解決方案。JAVA提供了一種特殊化的參數類型,稱作有限制的萬用字元類型(bounded wildcard type),來處理類似的情況。我們的想法是第二個add方法接收的參數應該是“E的某個子類型的Driver”,可通過Driver<? extends E>實現。?代表任意未知類型,附加extends E限制,表示該未知類型必須是E的子類或其自身。修改後的代碼如下:
第二版
public void add(Driver<? extends E> producer) { container.add(producer.getDrivingCar()); }這樣修改後,示範程式可正常能過編譯,也能正常運行,說明這樣是型別安全的。
上面第一次出現跟泛型相關的萬用字元?,表示未知類型,下面簡單說下其用法。
?
? 一般出現在方法參數上,代表未知類型。注意與泛型聲明中的E區別。泛型E相當於一個類型預留位置,可以在類中多處出現並意味著這幾處將來的具體類型是一樣的(例如Driver類和CarContainer中的泛型);而?代表某個未知的具體類型,類多個地方出現的?沒有任何關聯。
?支援<? extends Parent>用法,代表未知類型必須是Parent的子類或自身,與泛型中的<E extends Parent>意義很相似。
?還支援<? super Child>用法,代表未知類型必須是Child的超類或自身,這種用法在泛型中不存在(因為無意義),請注意。
上面總結中提到<? super Child>用法,接下來補充學習下super的使用情境。
對於CarContainer類,跟add方法對應的,我們新增一個pop方法,如下:
public void pop(Set<E> consumer){ consumer.add(container.remove(container.size())); }接收集合類consumer參數,方法體中從container中移除最後一個元素並加入到consumer中。使用樣本如下:
CarContainer<Runnable> container = new CarContainer<Runnable>(); Buick buick = new Buick(); container.add(buick); Set<Runnable> consumer = new HashSet<Runnable>(); container.pop(consumer);
假如你有另一個Set<Object> consumer = new HashSet<Object>()呢,如果按照上面調用,會出現編譯錯誤:
The method pop(Set<Runnable>) in the type CarContainer<Runnable> is not applicable for the arguments (Set<Object>)
我們的想法是,pop接收的參數應用是"E的某種超類的Set",通過?和super關鍵字配合使用正好可以達到這種目的,即Set<? super E>,修改後代碼如下:
第三版
public void pop(Set<? super E> consumer){ consumer.add(container.remove(container.size())); }修改後,支援了Set<Object> consumer調用方式。
通過?跟extends或super關鍵字在方法參數上的搭配使用,可以獲得API最大限度的靈活性。
那麼什麼情況下該使用extends關鍵字,什麼情況下該使用super關鍵字。有一個原則叫PECS,即producer-extends,consumer-super。
PECS意思是,如果帶泛型參數化型別表示一個E生產者,就使用<? extends E>;如果它表示一個E消費者,就使用<? super E>。在上述樣本中,add的producer參數產生E的執行個體供CarContainer使用,因此producer相應的類型為Driver<? extends E>;pop的consumer參數通過CarContainer消費E執行個體,因此consumer參數相應的類型為Set<? super E>。如果參數即是生產者又是消費者呢,那就不適合使用萬用字元?,因為你需要的是嚴格的類型匹配,即直接使用Set<E>
上面關於泛型的萬用字元?、extends和super的使用方法參照了《Effective.Java_2中文版.pdf》一書,由於水平有限,可能大家還沒看明白。歡迎大家提問。
關於這講實踐示範,暫時在項目中沒有用到,以後補充
結束語
關於泛型的學習和實踐到此結束,總共四節,謝謝大家的關注。等有時間了,想再跟大家一起學習下設計模式,我覺得這也是每個Java程式員的必修課。敬請期待