《Effective Java》學習筆記四——泛型
宣告中具有一個或者多個型別引數的類或者介面,就是泛型類或者介面。泛型類和介面統稱為泛型。
每種泛型定義一組引數化的型別。每個泛型都定義一個原生態型別,即不帶任何實際型別引數的泛型名稱。
如果使用原生態型別,就失掉了泛型在安全性和表述性方面的所有優勢。如果使用像List這樣的原生態型別,就會失掉型別安全性,但是如果使用像List\<Object>這樣的引數化型別,則不會。
無限制的萬用字元型別。如果要使用泛型,但是不確定或者不關心實際的型別引數,就可以使用一個問號代替。萬用字元型別是安全的,原生態型別則不安全。由於可以將任何元素放進使用原生態型別的集合中,因此很容易破壞該集合的型別約束條件。
不要在新程式碼中使用原生態型別,有兩個小例外,兩者都源於“泛型資訊可以再執行時被擦除”這一事實。在類文字中必須使用原生態型別,規範不允許使用引數化型別。換句話說,List.class,String[].class和int.class都合法,但是List\<String>.class和List\<?>.class則不合法。第二個例外與instanceof操作符有關。由於泛型資訊可以在執行時被擦除,因此在引數化型別而非無限制萬用字元型別上使用instanceof操作符是非法的。
總之,使用原生態型別會在執行時導致異常,因此不要在新程式碼中使用。原生態型別只是為了與引入泛型之前的遺留程式碼進行相容性和互用而提供的。
快速回顧:Set\<Obejct>是個引數化型別,表示可以包含任何物件型別的一個集合;Set\<?>則是一個萬用字元型別,表示只能包含某種未知物件型別的一個集合;Set則是個原生態型別,它脫離了泛型系統。前兩種是安全的,最後一種不安全。
術語 | 示例 |
---|---|
引數化的型別 | List\<String> |
實際型別引數 | String |
泛型 | List\<E> |
形式型別引數 | E |
無限制萬用字元型別 | List\<?> |
原生態型別 | List |
有限制類型引數 |
\ |
遞迴型別限制 | \<T extends Comparable\<T>> |
有限制萬用字元型別 | List\<?extends Number> |
泛型方法 | static \<E>List\<E> asList(E[] a) |
型別令牌 | String.class |
消除非受檢警告
用泛型程式設計時,會遇到許多編譯器警告:非受檢強制轉化警告、非受檢方法呼叫警告、非受檢普通資料建立警告,以及非受檢轉換警告。
要儘可能地消除每一個非受檢警告。
如果無法消除警告,同時可以證明引起警告的程式碼是型別安全的,(只有在這種情況下才)可以用一個@SuppressWarnings(“unchecked”)註解來禁止這條警告。
SuppressWarnings註解可以用在任何粒度的級別中,從單獨的區域性變數宣告到整個都可以。應該始終在儘可能小的範圍中使用SuppressWarnings註解。永遠不要在整個類上使用SuppressWarnings。
每當使用SuppressWarnings(“unchecked”)註解時,都要新增一條註釋,說明為什麼這麼做是安全的。
列表優先於陣列
陣列與泛型相比,有兩個重要的不同點。首先,陣列是協變的,即,如果Sub是Super的子型別,那麼陣列型別Sub[]就是Super[]的子型別。相反,泛型則是不可變的:對於任意兩個不同的型別Type1和Type2,List\<Type1>既不是List\<Type2>的子型別,也不是List\<Type2>的超型別。這實際上意味著陣列是有缺陷的。
利用陣列,為陣列元素賦值時,若元素型別不匹配,則要在執行時才能發現所犯的錯誤;但是利用列表,則可以在編譯時發現錯誤。
陣列與泛型之間的第二大區別在於,資料是具體化的。因此陣列會在執行時才知道並檢查它們的元素型別約束。
建立泛型陣列是非法的。因為它不是型別安全的。
當你得到泛型陣列建立錯誤時,最好的解決辦法通常是優先使用集合型別List\<E>,而不是陣列型別E[]。這樣可能會損失一些效能或者簡潔性,但是換回的卻是更高的型別安全性和互用性。
優先考慮泛型
使用泛型比使用需要在客戶端程式碼中進行轉換的型別來得更加安全,也更加容易。在設計新型別的時候,要確保它們不需要這種轉換就可以使用。這通常意味著要把類做成是泛型的。只要時間允許,就把現有的型別都泛型化。這對於這些型別的新使用者來說會變得更加輕鬆,又不會破壞現有的客戶端。
優先考慮泛型方法
靜態工具方法尤其適合於泛型化。Collections中的所有“演算法”方法都泛型化了。
泛型方法的一個顯著特徵是,無需明確指定型別引數的值,不像呼叫泛型構造器的時候是必須指定的。編譯器通過檢查方法引數的型別來計算型別引數的值。
相關的模式是泛型單例工廠。有時,會需要建立不可變但又適合於許多不同型別的物件。由於泛型是通過擦除實現的,可以給所有必要的型別引數使用單個物件,但是需要編寫一個靜態工廠方法,重複地給每個必要的型別引數分發物件。這種模式最常用於函式物件,如Collections.reverseOrder,但也適用於像Collections.emptySet這樣的集合。
利用有限制萬用字元來提升API的靈活性
為了獲得最大限度的靈活性,要在表示生產者或者消費者的輸入引數上使用萬用字元型別。
下面的助記符便於讓你記住要使用那種萬用字元型別:
PECS表示producer-extends,consumer-super。
如果引數化型別表示一個T生產者,就使用\<? extends T>;如果它表示一個T消費者,就使用\<? super T>。
如果使用得當,萬用字元型別對於類的使用者來說幾乎是無形的。他們使方法能夠接受它們應該接受的引數,並拒絕那些應該拒絕的引數。如果類的使用者必須考慮萬用字元型別,類的API或許就會出錯。
優先考慮型別安全的異構容器
有時會需要未限定固定數目的型別引數的容器,此時,可以將容器的鍵進行引數化而不是將容器引數化。然後將引數化的鍵交給容器來插入或者獲得值。用泛型系統來確保值的型別和它的鍵相符。