陣列與泛型的差異有兩項,一為陣列是協變的 (covariant),協變是在程式語言中父子關係的用詞,Java 中像是 Object 與 Long 就有繼承關係,所以 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");
協變與逆變陣列在運行時是知道元素的類型,泛型在編譯時強化類型,但在運行時是丟棄類型的,方便相容於 Java 5。基於以上的不同,泛型與陣列無法輕易的混用,像是 List<String>[] 與 new E [] 都不合法,為什麼不合法? 如果合法,會在運行時產生 ClassCastException,這樣就違反泛型提供的保證。範例如下,Chooser 在使用時,必須將 choose 方法回傳的 Object 轉成適用的型態。
https://zh.wikipedia.org/wiki/%E5%8D%8F%E5%8F%98%E4%B8%8E%E9%80%86%E5%8F%98
// 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
沒有留言:
張貼留言