引言
前三節講述了泛型常見聲明及使用,泛型既可以在類上進行聲明,也可以在單個方法上進行聲明,並分別對這兩種情況進行了總結。下面來學習下泛型擴展知識。
延用前面的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程序員的必修課。敬請期待