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 就試著把它泛型化 這會讓這類型的新用戶覺得易於使用 而且不會破壞現有的客戶端