java泛型學習和實踐(4)

引言

前三節講述了泛型常見聲明及使用,泛型既可以在類上進行聲明,也可以在單個方法上進行聲明,並分別對這兩種情況進行了總結。下面來學習下泛型擴展知識。

延用前面的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程序員的必修課。敬請期待

發佈了59 篇原創文章 · 獲贊 62 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章