《Effective Java》第5章 泛型
第23條:請不要在新代碼中使用原生態類型
聲明中具有一個或者多個類型參數( type parameter)的類或者接口,就是泛型(generic)類或者接口。
每種泛型定義一組參數化的類型(parameterized type),構成格式為: 先是類或者接口的名稱,接著用尖括號(<>)把對應於泛型形式類型參數的實際類型參數列表括起來。例如,List
最後點,每個泛型都定義一個原生態類型[raw type],即不帶任何實際類型參數的泛型名稱。例如,List
如果使用原生態類型,就失掉了泛型在安全性和表述性方面的所有優勢。
從Java 1.5發行版本開始,Java就提供了一種安全的替代方法,稱作無限制的通配符類型(unbounded wildcard type). 如果要使用泛型,但不確定或者不關心實際的類型參數,就可以使用一個問號代替。例如,泛型Set
通配符類型是安全的,原生態類型則不安全。
不能將任何元素(除了null之外)放到Colllection<>中。
不要在新代碼中使用原生態類型,這條規則有兩個小小的例外,兩者都源於“泛型信息可以在運行時被擦除”(見第25條)這一事實。在類文字(class literal)中必須使用原生態類型。規範不允許使用參數化類型(雖然允許數組類型和基本類型)。換句話說,List.class, String[].class和int.class都合法,但是List
這條規則的第二個例外與instanceof操作符有關。由於泛型信息可以在運行時被擦除,因此在參數化類型而非無限制通配符類型上使用instanceo躁作符是非法的。用無限制通配符類型代替原生態類型,對instanceof操作符的行為不會產生任何影響。在這種情況下,尖括號(<>)和問號(?)就顯得多余了。下面是利用泛型來使用instanceof操作符的首選方法:
第24條: 消除非受檢警告
SuppressWarnings註解可以用在任何粒度的級別中,從單獨的局部變量聲明到整個類都可以。應該始終在盡可能小的範圍中使用SuppressWarnings註解。它通常是個變量聲明、或是作常簡短的方法或者構造器。永遠不要在整個類上使用SuppressWarnings,這麽做可能會掩蓋了重要的警告。
如果你發現自己在長度不止行的方法或者構造器中使用了SuppressWarnings註解,可以將它移到一個局部變最的聲明中。
將SuppressWarnings註解放在return語句中是非法的,因為它不是一個聲明。
每當使用SuppressWarnings("unchecked")註解時,都要添加一條註釋,說明為什麽這麽做是安全的。
第25條:列表優先於數組
數組協變性的缺陷
數組與泛型相比。有兩個重要的不同點。首先,數組是協變的(covariant)。即表示如果Sub為Super的子類型,那麽數組類型Sub[]就是Super[]的子類型。相反,泛型則是不可變的(invariant): 對於任意兩個不同的類型Type1和Type2 , List
數組是具體的,泛型是擦除的
數組與泛型之間的第二大區別在於,數組是具體化的(reified )。因此數組會在運行時才知道並檢查它們的元素類型約束。相比之下,泛型則是通過擦除(erasure)來實現的。因此泛型只在編譯時強化它們的類型信息,並在運行時丟棄(或者擦除)它們的元素類型信息。
由於上述這些根本的區別,因此數組和泛型不能很好地混合使用。例如,創建泛型、參數化類型或者類型參數的數組是非法的。
非法定義的示例:
new List
Note:
為什麽創建泛型數組是非法的?因為它不是類型安全的。要是它合法,編譯器在其他正確的程序中發生的轉換就會在運行時失敗,並出現一個CIassCastException異常。這就違背了泛型系統提供的基本保證。
一個反例:
假設以下第一行是可以編譯通過的,那麽會在第五行得到CIassCastException。
創建無限制通配類型的數組是合法
從技術的角度來說,像E, List
列表:用性能換取類型安全性
當你得到泛型數組創建錯誤時,最好的解決辦法通常是優先使用集合類型List
總而言之,數組和泛型有著非常不同的類型規則。數組是協變且可以具體化的,泛型是不可變的且可以被擦除的。因此,數組提供了運行時的類型安全,但是沒有編譯時的類型安全,反之,對於泛型也樣。一般來說,數組和泛型不能很好地棍合使用。如果你發現自己將它們混合起來使用,並且得到了編譯時錯誤或者警告,你的第一反應就應該是用列表代替數組。
第27條:優先考慮泛型方法
類型導出
泛型方法的一個顯著特性是,無需明確指定類型參數的值,不像調用泛型構造器的時候是必須指定的。編譯器通過檢查方法參數的類型來計算類型參數的值。這個過程稱為類型導出(type inference)。
相關的模式是泛型單例工廠(generic singleton factory )。有時,會需要創建不可變但又適合於許多不同類型的對象。
第28條:利用有限制通配符來提升API的靈活性
例子:
假設我們想要在Stack中增加一個方法,讓它按順序將一系列的元素全部放到堆棧中。
考慮以下反例:
但是,如果嘗試這麽做,就會在以下代碼得到錯誤消息。因為如前所述,參數化類型是不可變的。
幸運的是,有一種解決辦法。Java提供了一種特殊的參數化類型,稱作有限制的通配符類型(bounded wildcard type),來處理類似的情況。pushAll的輸入參數類型不應該為“E的Iterable接口”,而應該為“E的某個子類型的Iterable接口”,有一個通配符類型正符合此意:
Iterable
修改後的代碼如下:
"E的某種超類的集合”(這裏的超類是確定的,因此E是它自身的一個超類), 仍然有一個通配符類型正是符合此意:Collection<? super E)。
PECS
結論很明顯口為了獲得最大限度的靈活性,要在表示生產者或者消費者的輸入參數上使用通配符類型。如果某個輸入參數既是生產者。又是消費者,那麽通配符類型對你就沒有什麽好處了:因為你需要的是嚴格的類型匹配,這是不用任何通配符而得到的。
下面的助記符便於讓你記住要使用哪種通配符類型:
PEGS表示producer-extends, consumer-super.
換句話說,如果參數化類型表示一個T生產者,就使用
Note:
不要用通配符類型作為返回類型。
總而言之,在API中使用通配符類型雖然比較需要技巧,但是使API變得靈活得多。如果編寫的是將被廣泛使用的類庫,則一定要適當地利用通配符類型。記住基本的原則:producer-extends, consumer-super (PECS)。還要記住所有的comparable和comparator都是消費者。
第29條:優先考慮類型安全的異構容器
異構容器是指能夠容納不同類型對象的容器。像我們通常用的List、Map等容器,它們的原生態類型本身就是異構容器,一旦給它們設置了泛型參數,例如List
使用Map實現類型安全的異構容器
我們將要實現一個Favorites類,用來對每個類型保存一個最喜歡的實例。它的API如下:
public class Favorites {
public <T> void putFavorite(Class<T> type, T instance);
public <T> T getFavorite(Class<T> type);
}
Favorite,的實現小得出奇。它的完整實現如下
但是,惡意的客戶端可以破壞Favorites實例的類型安全。如果客戶端傳入原生態的Class對象和不一致的值對象,則會在getFavorite的cast時拋出ClassCastException異常。不過好在我們可以對這一情況加以約束。只需要在put時使用一個動態的轉換就可以了:
將put方法更新為以下方法:
參考資料
【1】類型安全的異構容器 作者:金戈大王
《Effective Java》第5章 泛型