《Effective Java 3rd》3分鐘速成:(31-33) 泛型進階規則

前言:

Chapter 5. Generics

  • 31. 使用限定通配符來增加API的靈活性
  • 32. 合理地結合泛型和可變參數
  • 33. 優先考慮類型安全的異構容器

31. 使用限定通配符來增加API的靈活性


《Effective Java 3rd》3分鐘速成:(31-33) 泛型進階規則

泛型相關概念

為了獲得最大的靈活性,對代表生產者或消費者的輸入參數使用通配符類型。

通配符類型選擇規則:PECS 代表:

  • producer-extends:如果一個參數化類型代表一個 T 生產者,使用 extends T>;
  • consumer-super:如果它代表 T 消費者,則使用 super T>。
<code>// Wildcard type for a parameter that serves as an E producerpublic void pushAll(Iterable extends E> src) {    for (E e : src)        push(e);}// Wildcard type for parameter that serves as an E consumerpublic void popAll(Collection super E> dst) {    while (!isEmpty())        dst.add(pop());}/<code>

總結:

  • 1、在你的 API 中使用通配符類型,雖然棘手,但使得 API 更加靈活。
  • 2、如果編寫一個將被廣泛使用的類庫,正確使用通配符類型應該被認為是強制性的。
  • 3、記住基本規則: producer-extends, consumer-super(PECS)。 還要記住,所有 Comparable 和 Comparator 都是消費者。


32. 合理地結合泛型和可變參數

問題:

  • 1、在 Java 5 中,可變參數方法(詳見第 53 條)和泛型都被添加到平臺中,所以你可能希望它們能夠正常交互; 可悲的是,他們並沒有。
  • 2、可變參數:目的是允許客戶端將一個可變數量的參數傳遞給一個方法,但這是一個脆弱的抽象(leaky abstraction):當你調用一個可變參數方法時,會創建一個數組來保存可變參數;那個應該是實現細節的數組是可見的。
  • 3、因此,當可變參數具有泛型或參數化類型時,會導致編譯器警告混淆。

a. 泛型可變參數錯誤示例:

<code>// Mixing generics and varargs can violate type safety!static void dangerous(List<string>... stringLists) {    List<integer> intList = List.of(42);    Object[] objects = stringLists;    objects[0] = intList;             // Heap pollution(堆汙染:添加錯誤類型的元素)    String s = stringLists[0].get(0); // ClassCastException(會拋出運行時異常)}/<integer>/<string>/<code>

b. 替代方案1:代碼示例:

<code>// Safe method with a generic varargs parameter@SafeVarargsstatic  List flatten(List extends T>... lists) {    List result = new ArrayList<>();    for (List extends T> list : lists)        result.addAll(list);    return result;}/<code>

提醒一下,在下列情況下,泛型可變參數方法是安全的:

  • 1、它不會在可變參數數組中存儲任何東西;
  • 2、它不會使數組(或克隆)對不可信代碼可見。 如果違反這些禁令中的任何一項,請修復。

c. 替代方案2:可變參數->List 參數

<code>// List as a typesafe alternative to a generic varargs parameterstatic  List flatten(List<list>> lists) {    List result = new ArrayList<>();    for (List extends T> list : lists)        result.addAll(list);    return result;}audience = flatten(List.of(friends, romans, countrymen));/<list>/<code>

總結:

  • 1、可變參數和泛型不能很好地交互,因為可變參數機制是在數組上面構建的脆弱的抽象,並且數組具有與泛型不同的類型規則。
  • 2、雖然泛型可變參數不是類型安全的,但它們是合法的。 如果選擇使用泛型(或參數化)可變參數編寫方法,請首先確保該方法是類型安全的,然後使用 @SafeVarargs 註解對其進行標註,以免造成使用不愉快。
  • 3、用 List 參數替換可變參數;


33. 優先考慮類型安全的異構容器

常見需求:固定數量的類型參數的容器:

泛型的常見用法包括集合,如 Set 和 Map 和單個元素容器,如 ThreadLocal 和 AtomicReference。 在所有這些用途中,它都是參數化的容器。 這限制了每個容器只能有固定數量的類型參數。 通常這正是你想要的。 一個 Set 有單一的類型參數,表示它的元素類型; 一個 Map 有兩個,代表它的鍵和值的類型;等等。

進階需求:不固定數量的類型參數的容器(藉助參數化鍵(key)實現):

例如,數據庫一行記錄可以具有任意多列,並且能夠以類型安全的方式訪問它們是很好的。 幸運的是,有一個簡單的方法可以達到這個效果。 這個想法是參數化鍵(key)而不是容器。 然後將參數化的鍵提交給容器以插入或檢索值。 泛型類型系統用於保證值的類型與其鍵一致。

實現示例1:

<code>// Typesafe heterogeneous container pattern - implementationpublic class Favorites {    private Map<class>, Object> favorites = new HashMap<>();    public void putFavorite(Class type, T instance) {        favorites.put(Objects.requireNonNull(type), instance);    }    public T getFavorite(Class type) {        return type.cast(favorites.get(type));    }}// Typesafe heterogeneous container pattern - clientpublic static void main(String[] args) {    Favorites f = new Favorites();    f.putFavorite(String.class, "Java");    f.putFavorite(Integer.class, 0xcafebabe);    f.putFavorite(Class.class, Favorites.class);    String favoriteString = f.getFavorite(String.class);    int favoriteInteger = f.getFavorite(Integer.class);    Class> favoriteClass = f.getFavorite(Class.class);    System.out.printf("%s %x %s%n", favoriteString,        favoriteInteger, favoriteClass.getName());}/<class>/<code>

實現示例2(註解):

<code>public  T getAnnotation(Class annotationType);// Use of asSubclass to safely cast to a bounded type tokenstatic Annotation getAnnotation(AnnotatedElement element,                                String annotationTypeName) {    Class> annotationType = null; // Unbounded type token    try {        annotationType = Class.forName(annotationTypeName);    } catch (Exception ex) {        throw new IllegalArgumentException(ex);    }    return element.getAnnotation(        annotationType.asSubclass(Annotation.class));}/<code>

總結:

  • 1、泛型 API 的通常用法(以集合 API 為例)限制了每個容器的固定數量的類型參數。
  • 2、你可以通過將類型參數放在鍵上而不是容器上來解決此限制。 可以使用 Class 對象作為此類型安全異構容器的鍵。 以這種方式使用的 Class 對象稱為類型令牌。
  • 3、也可以使用自定義鍵類型。 例如,可以有一個表示數據庫行(容器)的 DatabaseRow 類型和一個泛型類型 Column 作為其鍵。


分享到:


相關文章: