第五章泛型

目錄

  • 不要使用原始類型
  • 消除非檢查警告
  • 列表優(yōu)于數(shù)組
  • 優(yōu)先考慮泛型
  • 優(yōu)先使用泛型方法
  • 使用限定通配符來(lái)增加API的靈活性
  • 合理地結(jié)合泛型和可變參數(shù)
  • 優(yōu)先考慮類型安全的異構(gòu)容器

泛型

不要使用原始類型

  • 如果你使用原始類型,則會(huì)喪失泛型的所有安全性和表達(dá)上的優(yōu)勢(shì)
  • 你必須在類字面值(class literals)中使用原始類型。egList.class,String [] .class和int.class都是合法的。o instanceof Set需要使用原始類型。
  • 限制類型參數(shù)<E extends Number》,遞歸類型限制<T extends Comparable<T>>,限制通配符類型List<? extends Number>,泛型方法 static <E> List<E> asList(E[] a)

消除非檢查警告

  • 如果你不能消除警告,但你可以證明引發(fā)警告的代碼是類型安全的,那么(并且只能這樣)用@SuppressWarnings(“unchecked”)注解來(lái)抑制警告
  • 聲明一個(gè)局部變量來(lái)保存返回值并標(biāo)注它的聲明
@SuppressWarnings("unchecked") T[] result =
            (T[]) Arrays.copyOf(elements, size, a.getClass());
retur result;

列表優(yōu)于數(shù)組

  • 數(shù)組提供運(yùn)行時(shí)類型的安全性,但不提供編譯時(shí)類型的安全性,反之亦然。 一般來(lái)說(shuō),數(shù)組和泛型不能很好地混合工作。 如果你發(fā)現(xiàn)把它們混合在一起,得到編譯時(shí)錯(cuò)誤或者警告,你的第一個(gè)沖動(dòng)應(yīng)該是用列表來(lái)替換數(shù)組
  • 數(shù)組是協(xié)變的,泛型是收約束的。
// 運(yùn)行時(shí)報(bào)錯(cuò)
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in"; // Throws ArrayStoreException

// 無(wú)法編譯通過(guò)
List<Object> ol = new ArrayList<Long>(); // Incompatible types
ol.add("I don't fit in");
  • 數(shù)組提供了運(yùn)行時(shí)類型安全性,不保證編譯時(shí)安全性,泛型則反過(guò)來(lái)

優(yōu)先考慮泛型

  • 嘗試創(chuàng)建Stack<int>或Stack<double>將導(dǎo)致編譯時(shí)錯(cuò)誤。 這是Java泛型類型系統(tǒng)的一個(gè)基本限制
  • 泛型類型比需要在客戶端代碼中強(qiáng)制轉(zhuǎn)換的類型更安全,更易于使用。 當(dāng)你設(shè)計(jì)新的類型時(shí),確保它們可以在沒(méi)有這種強(qiáng)制轉(zhuǎn)換的情況下使用
  • 這個(gè)類應(yīng)該已經(jīng)被參數(shù)化了,但是由于事實(shí)并非如此,我們可以對(duì)它進(jìn)行泛型化。 就目前而言,客戶端必須強(qiáng)制轉(zhuǎn)換從堆棧中彈出的對(duì)象,而這些強(qiáng)制轉(zhuǎn)換可能會(huì)在運(yùn)行時(shí)失敗。
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }
  • 可參考Stack對(duì)這種泛型的寫(xiě)法,它解決了初始化不能用new E()的問(wèn)題,用到了在返回ele時(shí)強(qiáng)轉(zhuǎn)Object到E。

優(yōu)先使用泛型方法

  • 泛型方法比需要客戶端對(duì)輸入?yún)?shù)和返回值進(jìn)行顯式強(qiáng)制轉(zhuǎn)換的方法更安全,更易于使用。 像類型一樣,你應(yīng)該確保你的方法可以不用強(qiáng)制轉(zhuǎn)換,這通常意味著它們是泛型的。
// 這種會(huì)有警告
public static Set union(Set s1, Set s2) {

    Set result = new HashSet(s1);

    result.addAll(s2);

    return result;
}

//這種是安全的
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {

    Set<E> result = new HashSet<>(s1);

    result.addAll(s2);

    return result;

}
  • 遞歸類型限制
public static <E extends Comparable<E>> E max(Collection<E> c) {
    if (c.isEmpty()) throw new IllegalArgumentException("Empty collection");
    E result = null;
    for (E e : c){
        if (result == null || e.compareTo(result) > 0){
            result = Objects.requireNonNull(e);
        }
    }
    return result;
}

使用限定通配符來(lái)增加API的靈活性

  • 參數(shù)化類型是不變的。換句話說(shuō),對(duì)于任何兩個(gè)不同類型的Type1和Type,List <Type1>既不是List <Type2>子類型也不是其父類型
  • 為了獲得最大的靈活性,對(duì)代表生產(chǎn)者或消費(fèi)者的輸入?yún)?shù)使用通配符類型
//考慮以下這種,如果pushAll方法是public void pushAll(Iterable<E> src) 則會(huì)報(bào)錯(cuò),因?yàn)閰?shù)化類型是不變的。
Stack<Number> numberStack = new Stack<>();

Iterable<Integer> integers = ... ;

numberStack.pushAll(integers);

//可以變成這種, 這樣增加靈活性。生成棧使用的E實(shí)例使用extend
public void pushAll(Iterable<? extends E> src)
// 消費(fèi)棧使用的E實(shí)例使用super 
public void popAll(Collection<? super E> dst)
// 所有Comparable和Comparator都是消費(fèi)者
  • 返回類型仍然是Set <E>。 不要使用限定通配符類型作為返回類型
  • 靈活性體現(xiàn)
// 無(wú)界類型參數(shù)
public static <E> void swap(List<E> list, int i, int j); 

// 無(wú)界通配符:該方式優(yōu)于上一個(gè)方式,但是由于無(wú)界通配符類型無(wú)法修改,即需要借助helper進(jìn)行修改,但這對(duì)于調(diào)用者無(wú)需關(guān)心。
public static void swap(List<?> list, int i, int j) { 
    swapHelper(list, i, j);
}

private static <E> void swapHelper(List<E> list, int i, int j) { 
    list.set(i, list.set(j, list.get(i)));
}
匯總
  1. 上邊界類型通配符(<? extends 父類型>):因?yàn)榭梢源_定父類型,所以可以以父類型去獲取數(shù)據(jù)(向上轉(zhuǎn)型)。但是不能寫(xiě)入數(shù)據(jù)。
  2. 下邊界類型通配符(<? super 子類型>):因?yàn)榭梢源_定最小類型,所以可以以最小類型去寫(xiě)入數(shù)據(jù)(向上轉(zhuǎn)型)。而不能獲取數(shù)據(jù)。
  3. 無(wú)邊界類型通配符(<?>) 等同于 上邊界通配符<? extends Object>,所以可以以O(shè)bject類去獲取數(shù)據(jù)。List list 相當(dāng)于List<Object> list

合理地結(jié)合泛型和可變參數(shù)

  • 可變參數(shù)和泛型不能很好地交互,因?yàn)榭勺儏?shù)機(jī)制是在數(shù)組(協(xié)變,)上面構(gòu)建的脆弱的抽象,并且數(shù)組具有與泛型不同的類型規(guī)則。 雖然泛型可變參數(shù)不是類型安全的,但它們是合法的。 如果選擇使用泛型(或參數(shù)化)可變參數(shù)編寫(xiě)方法,請(qǐng)首先確保該方法是類型安全的

優(yōu)先考慮類型安全的異構(gòu)容器

  • 泛型API的通常用法(以集合API為例)限制了每個(gè)容器的固定數(shù)量的類型參數(shù)。 你可以通過(guò)將類型參數(shù)放在鍵上而不是容器上來(lái)解決此限制。 可以使用Class對(duì)象作為此類型安全異構(gòu)容器的鍵。 以這種方式使用的Class對(duì)象稱為類型令牌。
  • 與普通Map不同,所有的鍵都是不同的類型。 因此,我們將Favorites稱為類型安全異構(gòu)容器
public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), instance);
    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}
  • 如果你嘗試保存你最喜歡的List <String>,程序?qū)⒉荒芫幾g。 原因是無(wú)法獲取List <String>的Class對(duì)象。 List <String> .class是語(yǔ)法錯(cuò)誤,也是一件好事。 List <String>和List <Integer>共享一個(gè)Class對(duì)象
  • 現(xiàn)有實(shí)踐是把各個(gè)Service類作為key

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 泛型的作用:告訴編譯器每個(gè)集合中可接受哪些對(duì)象類型,編譯器自動(dòng)地為你的插入進(jìn)行轉(zhuǎn)化,并在編譯時(shí)告知是否插入錯(cuò)誤的對(duì)...
    Timorous閱讀 294評(píng)論 0 0
  • Java1.5中添加了泛型。沒(méi)有泛型之前,從集合中讀取的每一個(gè)對(duì)象都必須進(jìn)行轉(zhuǎn)換,如果不小心添加了類型錯(cuò)誤的對(duì)象,...
    bunnypu閱讀 653評(píng)論 0 0
  • 泛型是.NET Framework2.0新增的一個(gè)特性,在命名空間System.Collections.Gener...
    張中華閱讀 329評(píng)論 0 3
  • 第8章 泛型 通常情況的類和函數(shù),我們只需要使用具體的類型即可:要么是基本類型,要么是自定義的類。但是在集合類的場(chǎng)...
    光劍書(shū)架上的書(shū)閱讀 2,201評(píng)論 6 10
  • object 變量可指向任何類的實(shí)例,這讓你能夠創(chuàng)建可對(duì)任何數(shù)據(jù)類型進(jìn)程處理的類。然而,這種方法存在幾個(gè)嚴(yán)重的問(wèn)題...
    CarlDonitz閱讀 1,029評(píng)論 0 5

友情鏈接更多精彩內(nèi)容