Effective Java Item29 - 優先考慮泛型
December 09, 2018這篇是Effective Java - Favor generic type章節的讀書筆記 本篇的程式碼來自於原書內容
Item29: 優先考慮泛型
一般來說 將集合聲明參數化 或是使用library提供的泛型方法 不會太困難 但要自己編寫泛型就需要多加練習
本篇會實際走過一個完整的泛型化的步驟 讓你知道怎麼讓一個類別實現泛型
Stack實現
看一下我們在Item7看到的Stack
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;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}你看到 elements是個物件數組 這就是個練習泛型的好目標
第一步: 給聲明添加類型參數
先在Stack 後面加上<E> 然後把所有類別裡面的Object換成E
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new E[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
E result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
... // no changes in isEmpty or ensureCapacity
}通常第一部做完 會看到不少錯誤 幸運的是這次只有一個

第二步: 消除錯誤
原因是你不能創建一個不能具體化的Array 編譯器根本不知道E是什麼
對於這種問題 有兩種主要的解決方法
解法1
創一個Object Array 後再強制轉型
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}寫完後 從錯誤變成警告

編譯器無法證明這個轉換是typesafe 但是我們可以 原因如下
1.可能會產生問題的elements是private 永遠不會傳給客戶
2.會寫進elements的唯一方法是push 而push的傳入參數是E
所以我們可以確定 這個強制Cast很安全 所以我們可以加上註解來抑制警告 別忘了當你要抑制警告時 盡可能縮小範圍
// The elements array will contain only E instances from push(E).
// This is sufficient to ensure type safety, but the runtime
// type of the array won't be E[]; it will always be Object[]!
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}解法2
把elements從E[] 變成Object[]
既然elements是Object[] 那constructor裡就沒問題了 問題在pop
一樣我們強制轉型(E) 看到了警告

抑制它
// Appropriate suppression of unchecked warning
public E pop() {
if (size == 0)
throw new EmptyStackException();
// push requires elements to be of type E, so cast is correct
@SuppressWarnings("unchecked") E result =
(E) elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}兩種解法比較
兩種解法都有追隨者 第一種明確的定義elements是E[] 而且只需要在constructor中處理好就可以
但第二種 因為elements是Object[] 所以在每次讀取的時候 你都必須要Cast
下面的程式展示了如何使用我們的泛型Stack
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
for (String arg : args)
stack.push(arg);
while (!stack.isEmpty())
System.out.println(stack.pop().toUpperCase());
}Stack<E>裡面的E可以是任何東西 Stack <Object> Stack <int []> Stack <List <String >>
但不能是primitive type 比如說Stack<int> Stack<long> 你只能用Stack<Integer>或Stack<Long>代替
結論
使用泛型 比使用強制轉換更安全 當你手上有一些現有的類型應該要被泛型化 像是本文一開始的Stack 就試著把它泛型化 這會讓這類型的新用戶覺得易於使用 而且不會破壞現有的客戶端