2020/08/20

[筆記] Effective Java #28 List 優先於陣列

Effective Java 3rd 簡體中文版筆記 #28 List 優先於陣列
陣列與泛型的差異有兩項,一為陣列是協變的 (covariant),協變是在程式語言中父子關係的用詞,Java 中像是 ObjectLong 就有繼承關係,所以 Long[] 可以是 Object[] 的子類型。相反的泛型是不變的 (invariant)List<Long> 不是 List<Object> 的子型態,所以在宣告就會發生錯誤,但陣列部分在運行時才發生錯誤。
// Fails at runtime!
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in";  // Throws ArrayStoreException

// Won't compile!
List<Object> ol = new ArrayList<Long>();  // Incompatible types
ol.add("I don't fit in");
協變與逆變
https://zh.wikipedia.org/wiki/%E5%8D%8F%E5%8F%98%E4%B8%8E%E9%80%86%E5%8F%98
陣列在運行時是知道元素的類型,泛型在編譯時強化類型,但在運行時是丟棄類型的,方便相容於 Java 5。基於以上的不同,泛型與陣列無法輕易的混用,像是 List<String>[]new E [] 都不合法,為什麼不合法? 如果合法,會在運行時產生 ClassCastException,這樣就違反泛型提供的保證。範例如下,Chooser 在使用時,必須將 choose 方法回傳的 Object 轉成適用的型態。
// Chooser - a class badly in need of generics!
public class Chooser {
    private final Object[] choiceArray;

    public Chooser(Collection choices) {
        choiceArray = choices.toArray();
    }

    public Object choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceArray[rnd.nextInt(choiceArray.length)];
    }
}
今天若要將它修改成泛型,首先先將 Object 替換成 T,但是在 choices.toArray() 需修改為 (T[]) choices.toArray()
public class Chooser<T> {
    private final T[] choiceArray;

    public Chooser(Collection<T> choices) {
        choiceArray = (T[]) choices.toArray();
    }
    // choose method unchanged
}
上面程式碼在編譯時,會跳出 unchecked 警告,因為沒辦法確定 T 是什麼,可以使用 @SuppressWarnings("unchecked") 來消除警告,但是最好還是將 T[] 修改為 List<T>。這樣的程式碼稍微複雜一點,速度可能也會稍慢,但不會發生 ClassCastException
// List-based Chooser - typesafe
public class Chooser<T> {
    private final List<T> choiceList;

    public Chooser(Collection<T> choices) {
        choiceList = new ArrayList<>(choices);
    }

    public T choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size()));
    }
}
轉載請註明原文網址 https://cookieandcoketw.blogspot.com/2020/08/effective-java-28-list-array.html

沒有留言:

張貼留言