程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 使用通配符簡化泛型使用

使用通配符簡化泛型使用

編輯:關於JAVA

在使用 Java 語言的泛型時,通配符非常令人困惑,並且最常見的一個錯誤就是在使用有界通配符的兩種形式的其中之一(“? super T” 和 “? extends T”)時出現錯誤。您出錯了嗎?別沮喪,即使是專家也會犯這種錯誤,本月 Brian Goetz 將展示如何避免這個錯誤。

在 Java 語言中,數組是協變的(因為一個 Integer 同時也是一個 Number,一個 Integer 數組同時也是一個 Number 數組),但是泛型不是這樣的(List並不等於 List)。人們會爭論哪些選擇是 “正確的”,哪些選擇是 “錯誤的” —當然,每種選擇都各有優缺點 —但有一點毫無疑問,存在兩種使用差別很小的語義構造派生類型的類似機制,這將導致大量錯誤和誤解。

有界通配符(一些有趣的 “? extends T” 通用類型說明符)是語言提供的一種工具,用來處理協變性缺乏 —有界通配符允許類聲明方法參數或返回值何時具有協變性(或相反,聲明方法參數或返回值何時具有逆變性(contravariant))。雖然了解何時使用有界通配符是泛型較為復雜的方面,但是,使用有界通配符的壓力通常都落在庫作者的身上,而非庫用戶。最常見的有界通配符錯誤就是忘記使用它們,這就限制了類的使用,或是強制用戶不得不重用現有的類。

有界通配符的作用

讓我們從一個簡單的泛型類開始(一個稱為 Box 的值容器),它持有一個具有已知類型的值:

public interface Box{

public T get();

public void put(T element);

}

由於泛型不具備協變性,Box並不等同於 Box,盡管 Integer 屬於 Number。但是對於 Box 這樣的簡單泛型類來說,這不成問題,並且常常被忽略,因為 Box的接口完全指定為 T 類型的變量 —而不是通過 T 泛型化的類型。直接處理類型變量允許實現多態性。清單 1 展示了這種多態性的兩個示例:獲取 Box的內容,並將它作為一個 Number,然後將一個 Integer 放入 Box中:

清單 1. 通過泛型類利用固有的多態性

BoxiBox = new BoxImpl(3);

Number num = iBox.get();

BoxnBox = new BoxImpl(3.2);

Integer i = 3;

nBox.put(i);

通過使用簡單的 Box 類,使我們確信可以沒有協變性,因為在需要實現多態的位置,數據已經具有某種形式,使編譯器能夠應用適當的子類型規則。

然而,如果希望 API 不僅能夠處理 T 類型的變量,還能處理通過 T 泛型化的類型,事情將變得更加復雜。假設希望將一個新的方法添加到 Box,該方法允許獲得另一個 Box 的內容並其放到清單 2 所示的 Box 中:

清單 2. 擴展的 Box 接口並不靈活

public interface Box{

public T get();

public void put(T element);

public void put(Boxbox);

}

這個擴展 Box 的問題是,只能將內容放到類型參數與原 box 完全相同的 Box 中。因此,清單 3 中的代碼就不能進行編譯:

清單 3. 泛型不具備協變性

BoxnBox = new BoxImpl();

BoxiBox = new BoxImpl();

nBox.put(iBox); // ERROR

顯示一條錯誤消息,表示無法在 Box中找到方法 put(Box)。如果認為泛型是不具有協變性的,這條錯誤還講得通;一個 Box不是 Box,盡管 Integer 是 Number,但是這使得 Box 類的 “泛型性” 比我們期望的要弱。要提高泛型代碼的有效性,可以指定一個上限(或下限),而不是指定某個泛型類型參數的精確類型。這可以使用有界通配符來實現,它的形式為 “? extends T” 或 “? super T”。(有界通配符只能用作類型參數,而不能作為類型本身 —因此,需要一個有界的命名的類型變量)。在清單 4 中,修改了 put() 的簽名以使用一個上限通配符 —Box,這表示 Box 的類型參數可以是 T 或 T 的任何子類。

清單 4. 對清單 3 的 Box 類的改進解釋了協變性

public interface Box{

public T get();

public void put(T element);

public void put(Boxbox);

}

現在,清單 3 中的代碼可以進行編譯並執行,因為 put() 的參數現在可以是參數類型為 T 或 T 的子類型的 Box。由於 Integer 是 Number 的子類型,編譯器能夠解析方法引用 put(Box),因為 Box匹配有界通配符 Box。

很容易犯清單 3 中的 Box 錯誤,即使是專家也難以避免 —在平台類庫中,許多地方都使用 Collection,而不是 Collection。例如,在 java.util.concurrent 包的 AbstractExecutorService 中,invokeAll() 的參數最初是一個 Collection>。但是,這樣使用 invokeAll() 非常麻煩,因為這要求必須由 Callable參數化的集合持有任務集,而不是由實現 Callable的類參數化的集合。在 Java 6 中,這種簽名被修改為 Collection>—這只是為了演示非常容易犯這個錯誤,正確的修復應該是使 invokeAll() 包含一個 Collection>參數。這個參數無疑更加難看,但不會給客戶機帶來麻煩。

下限通配符

上面的大多數有界通配符都進行了限定;“? extends T” 符號為類型添加了一個上限。但是,雖然比較少見,仍然可以使用 “? super T” 符號為類型添加一個下限,表示 “類型 T 以及它的任何超類”。當您希望指定一個回調對象(例如一個比較器)或存放某個值的數據結構,可以使用下限通配符。

假設我們希望增強 Box,使它能夠與另一個 box 的內容進行比較。可以通過 containsSame() 方法和 Comparator 回調對象的定義擴展 Box,如清單 5 所示:

清單 5. 嘗試向 Box 添加一個比較方法

public interface Box{

public T get();

public void put(T element);

public void put(Boxbox);

boolean containsSame(Boxother,

EqualityComparatorcomparator);

public interface EqualityComparator{

public boolean compare(T first, T second);

}

}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved