前言:
Chapter 5. Generics
- 31. 使用限定通配符來增加API的靈活性
- 32. 合理地結合泛型和可變參數
- 33. 優先考慮類型安全的異構容器
31. 使用限定通配符來增加API的靈活性
![《Effective Java 3rd》3分鐘速成:(31-33) 泛型進階規則](http://p2.ttnews.xyz/loading.gif)
泛型相關概念
為了獲得最大的靈活性,對代表生產者或消費者的輸入參數使用通配符類型。
通配符類型選擇規則: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@SafeVarargsstaticList /<code>flatten(List extends T>... lists) { List result = new ArrayList<>(); for (List extends T> list : lists) result.addAll(list); return result;}
提醒一下,在下列情況下,泛型可變參數方法是安全的:
- 1、它不會在可變參數數組中存儲任何東西;
- 2、它不會使數組(或克隆)對不可信代碼可見。 如果違反這些禁令中的任何一項,請修復。
c. 替代方案2:可變參數->List 參數
<code>// List as a typesafe alternative to a generic varargs parameterstaticList /<code>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>
總結:
- 1、可變參數和泛型不能很好地交互,因為可變參數機制是在數組上面構建的脆弱的抽象,並且數組具有與泛型不同的類型規則。
- 2、雖然泛型可變參數不是類型安全的,但它們是合法的。 如果選擇使用泛型(或參數化)可變參數編寫方法,請首先確保該方法是類型安全的,然後使用 @SafeVarargs 註解對其進行標註,以免造成使用不愉快。
- 3、用 List 參數替換可變參數;
33. 優先考慮類型安全的異構容器
常見需求:固定數量的類型參數的容器:
泛型的常見用法包括集合,如 Set
進階需求:不固定數量的類型參數的容器(藉助參數化鍵(key)實現):
例如,數據庫一行記錄可以具有任意多列,並且能夠以類型安全的方式訪問它們是很好的。 幸運的是,有一個簡單的方法可以達到這個效果。 這個想法是參數化鍵(key)而不是容器。 然後將參數化的鍵提交給容器以插入或檢索值。 泛型類型系統用於保證值的類型與其鍵一致。
實現示例1:
<code>// Typesafe heterogeneous container pattern - implementationpublic class Favorites { private Map<class>, Object> favorites = new HashMap<>(); publicvoid putFavorite(Class /<class>/<code>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());}
實現示例2(註解):
<code>publicT getAnnotation(Class /<code>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));}
總結:
- 1、泛型 API 的通常用法(以集合 API 為例)限制了每個容器的固定數量的類型參數。
- 2、你可以通過將類型參數放在鍵上而不是容器上來解決此限制。 可以使用 Class 對象作為此類型安全異構容器的鍵。 以這種方式使用的 Class 對象稱為類型令牌。
- 3、也可以使用自定義鍵類型。 例如,可以有一個表示數據庫行(容器)的 DatabaseRow 類型和一個泛型類型 Column
作為其鍵。
閱讀更多 編程家園 的文章