Effective Java : 泛型

23.不要在新代碼中使用原生態類型

簡介

  • Java泛型從1.5引入,爲了保持兼容性,實現的是僞泛型,類型參數信息在編譯完成之後都會被擦除,其在運行時的類型都是raw type,類型參數保存的都是Object類型,List<E>raw type就是List
  • 編譯器在編譯期通過類型參數,爲讀操作自動進行了類型強制轉換,同時在寫操作時自動進行了類型檢查
  • 如果使用raw type,那編譯器就不會在寫操作時進行類型檢查了,寫入錯誤的類型也不會報編譯錯誤,那麼在後續讀操作進行強制類型轉換時,將會導致轉換失敗,拋出異常

ListList<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];

解決這種問題,有兩種方式

  1. 創建一個 Object數組,並轉換成 泛型數組類型

  2. 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());
    }
  1. 這樣的化, 這樣就可以將 EE的子類放入 集合中.popAll同理
  2. 因爲泛型是不可變的,就好像 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的方式解決
  • 所有的 comparablecomparator都是消費者.
// 不導出,對用戶不可見
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

總結

泛型限制了 容器使用固定數目的 類型參數,我們可以將類型參數放在 鍵上,而不是容器上,來避開這一限制,
這種方式叫做 異構容器

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章