23.不要在新代碼中使用原生態類型
簡介
Java泛型從1.5
引入,爲了保持兼容性,實現的是僞泛型,類型參數信息在編譯完成之後都會被擦除,其在運行時的類型都是raw type
,類型參數保存的都是Object
類型,List<E>
的raw type
就是List
- 編譯器在編譯期通過類型參數,爲讀操作自動進行了類型強制轉換,同時在寫操作時自動進行了類型檢查
- 如果使用
raw type
,那編譯器就不會在寫操作時進行類型檢查了,寫入錯誤的類型也不會報編譯錯誤,那麼在後續讀操作進行強制類型轉換時,將會導致轉換失敗,拋出異常
List
與List<Object>
- 前者不具備類型安全性,後者具備
- 可以將
List<String>
傳遞給List
的參數,但不能傳遞給List<Object>
的參數 - 泛型有子類化(
subtyping
)規則,List<String>
是List
的子類型,但不是List<Object>
的子類型
Set<?>
與Set
區別
當不確定類型參數,或者說類型參數不重要時,也不應該使用
raw type
,而應該使用List<?>
- 任何參數化的
List
均是List<?>
的子類 - 通配符是類型安全的,原生態是不安全的/
- 被引用的
List<?>
,並不能向其中加入任何非null
元素,讀取出來的元素也是Object
類型,而不會被自動強轉爲任何類型 instanceof
不支持泛型
術語:
24.消除非受檢警告
原則
- 要儘可能的消除每一個 非受檢警告
- 清除警告能夠確保不會出現
ClassCastException
- 如果無法消除警告,且確信是安全的,可添加註解
@SuppressWarnings("unchecked")
- 應該儘量
減小注解作用的範圍
,通常是應該爲一個賦值語句添加註解
- 非受檢警告很重要,不要忽視他們,每一條警告都預示着這裏會在運行時出現一些問題.
25.列表優於數組
簡介
- 數則是協變的,而泛型是不可變的,如下示例:
// Fails at runtime
Object[] objects = new Long[1];
objects[0] = "Throws ArrayStoreException";
// Won`t compile
List<Object> objectList = new ArrayList<Long>();
objectList.add("Incompatible types");
- 數組是具體化的,只有在
運行時
才能知道並檢查他們的元素類型約束
. - 泛型則是通過
擦除
來實現,在編譯時就知道元素的類型 - 不能返回
泛型元素
的數組,必須是具體化類型(即E[]
是類型不安全的) - 在結合使用
可變參數方法
和泛型
時會出現令人費解的警告
總結
- 數組是協變且可以具體化的.泛型是不可變且可以被擦除的.
- 數組提供了
運行時
類型安全,泛型提供了編譯時
類型安全
26.優先考慮泛型
簡介
第25 條 說明不能創建
不可具體化
類型的數組,如E[]
.如下面這種情況:
elements = new E[DEFAULT_CAPACITY];
解決這種問題,有兩種方式
創建一個
Object
數組,並轉換成泛型數組類型
將
elements
域的 類型 從E
改爲Object[]
這兩種方式編譯器都無法再運行時類型檢驗,因此需要自己添加註解.
@SuppressWarning("unchecked")
elements = (E[])new Object[DEFAULT_CAPACITY];
總結
- 有時看似與
item 25
矛盾,實屬無奈,Java
原生沒有List
,原生只支持數組,ArrayList
不得不基於數組實現,HashMap
也是基於數組實現的 - 泛型不支持原生類型,比如
List<int>
會報編譯時錯誤,使用包裝類型即可解決.List<Integer>
- 泛型比使用者進行
cast
更加安全,而且由於Java泛型的擦除
實現,也可以和未做泛型的老代碼無縫兼容
27.優先考慮泛型方法
簡介
靜態工具方法尤其
適合
用泛型
化,編寫泛型方法
與編寫泛型類型
類似.
// Generic method
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
- 泛型方法無需指定
類型參數
的值.利用有限制的通配符
可以使方法變得更加靈活. - 類型安全如
<T extends Comparable<T>>
,可以讀作”針對可以與自身進行比較每個類型T”.
總結
- 泛型方法像
泛型類
一樣,需要傳入泛型參數
- 爲了消除
泛型聲明冗餘
,可以使用靜態工廠方法,如下:
Map<String,List<String>> map = new HashMap<String,List<String>>();
public static <K, V> HashMap<K, V> newHashMap() {
return new HashMap<K, V>();
}
- 方法使用者就不用類型轉換即可使用
28.利用有限制通配符限制API的靈活性
語義:
使用
<? extends T>
: 表示T生產者
或者<? super E>
: 表示E消費者
不要用通配符
作爲返回值 類型.
示例
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
- 這樣的化, 這樣就可以將
E
和E的子類
放入 集合中.popAll
同理- 因爲泛型是不可變的,就好像
List<String>
不是List<Object>
的子類一樣.但可以通過這種方式來解決.
- 下面是一個通過
使用通配符
修正的類型聲明
//遵循了開頭的三個規則
public static <T extends Comparable<? super T>> T max(List<? extends T> list{}
顯示的類型參數
如果編譯器不能推斷
類型.我們也可以顯示的指定類型
,如:
Set<Number> numbers = Union.<Number>(integers,doubles);
建議
public static <E> void swap(List<E> list,int i,int j);
public static void swap(List<?> list,int i,int j);//使用通配符
- 如上所示,如果類型參數
只在方法聲明中出現
一次,應該用通配符
替代 - 但是上面的
通配符
方法會編譯錯誤.不能把null之外的任何值
放進List<?>
(參考第23
條),通常通過helper
的方式解決 - 所有的
comparable
和comparator
都是消費者.
// 不導出,對用戶不可見
private static <E> void swapHelper(List<E> list,int i,int j){
list.set(i,list.set(j,list.get(i));
}
public static void swap(List<?> list,int i,int j){
swapHelper(list,i,j);
};
29.優先考慮類型安全的異構容器
介紹
使用泛型限制了
每個容器只能有
固定數目的類型參數
,有時候我們需要跟過的靈活性,比如 如下示例:
示例: Favorites.java
public <T> void putFavorite(Class<T> type, T instance) {}
public <T> T getFavorite(Class<T> type) {}
這裏,
存入和取出
的是不同類型 也可以滿足,而且保證了類型安全
,而不是像Map<K,V>
在聲明時就將類型固定
了,這種 被稱爲類型安全的異構容器
.
TypeToken
自 1.5
開始 java
支持 Class<T>
,即 :
String.class 屬於 Class<String> 類型
Integer.class 屬於 Class<Integer> 類型
這種 當一個類 的字面文字 被用在方法中,來傳達 編譯時和運行時的類型信息時.被稱作
type token
解惑
private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
public <T> void putFavorite(Class<T> type, T instance) {
if (type == null)
throw new NullPointerException("Type is null");
favorites.put(type, instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
如上,雖然
Class<?>
使用了 通配符,卻 不涉及到使用通配符容器不能存入任何非null的元素
[參考 23 條], 因爲它屬於鍵key
,而不是map 容器
侷限
Favorites
類並不是運行時安全
的.如原生態類型的
類,HashSet等,可如下實現安全的運行時類型.
public <T> void putFavorite(Class<T> type, T instance) {
if (type == null)
throw new NullPointerException("Type is null");
favorites.put(type, type.cast(instance));
}
如上這種技巧,普遍的用在了
checkedList
,checkedMap
中.
Favorites
類不能用在不可具體化的
類型中.就是說,可以保存String
,String[]
,但是不能保存List<String>
,因爲List<String>.class
是不合法的.儘管可以有TypeToken
總結
泛型限制了 容器
使用固定數目的 類型參數,我們可以將類型參數
放在 鍵上
,而不是容器上,
來避開這一限制,
這種方式叫做 異構容器