Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必不少人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到如今已經將近8年的時間,但隨着Java 6,7,8,甚至9的發佈,Java語言發生了深入的變化。
在這裏第一時間翻譯成中文版。供你們學習分享之用。java
參數化聲明並使用JDK提供的泛型類型和方法一般不會太困難。 但編寫本身的泛型類型有點困難,但值得努力學習。程序員
考慮條目 7中的簡單堆棧實現:數組
// Object-based collection - a prime candidate for generics 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); } }
這個類應該已經被參數化了,可是因爲事實並不是如此,咱們能夠對它進行泛型化。 換句話說,咱們能夠參數化它,而不會損害原始非參數化版本的客戶端。 就目前而言,客戶端必須強制轉換從堆棧中彈出的對象,而這些強制轉換可能會在運行時失敗。 泛型化類的第一步是在其聲明中添加一個或多個類型參數。 在這種狀況下,有一個類型參數,表示堆棧的元素類型,這個類型參數的常規名稱是E
(條目 68)。安全
下一步是用相應的類型參數替換全部使用的Object類型,而後嘗試編譯生成的程序:性能
// Initial attempt to generify Stack - won't compile! 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 }
你一般會獲得至少一個錯誤或警告,這個類也不例外。 幸運的是,這個類只產生一個錯誤:學習
Stack.java:8: generic array creation elements = new E[DEFAULT_INITIAL_CAPACITY]; ^
如條目 28所述,你不能建立一個不可具體化類型的數組,例如類型E
。每當編寫一個由數組支持的泛型時,就會出現此問題。 有兩種合理的方法來解決它。 第一種解決方案直接規避了對泛型數組建立的禁用:建立一個Object數組並將其轉換爲泛型數組類型。 如今沒有了錯誤,編譯器會發出警告。 這種用法是合法的,但不是(通常)類型安全的:ui
Stack.java:8: warning: [unchecked] unchecked cast found: Object[], required: E[] elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; ^
編譯器可能沒法證實你的程序是類型安全的,但你能夠。 你必須說服本身,不加限制的類型強制轉換不會損害程序的類型安全。 有問題的數組(元素)保存在一個私有屬性中,永遠不會返回給客戶端或傳遞給任何其餘方法。 保存在數組中的惟一元素是那些傳遞給push
方法的元素,它們是E
類型的,因此未經檢查的強制轉換不會形成任何傷害。命令行
一旦證實未經檢查的強制轉換是安全的,請儘量縮小範圍(條目 27)。 在這種狀況下,構造方法只包含未經檢查的數組建立,因此在整個構造方法中抑制警告是合適的。 經過添加一個註解來執行此操做,Stack能夠乾淨地編譯,而且能夠在沒有顯式強制轉換或擔憂ClassCastException異常的狀況下使用它:翻譯
// 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]; }
消除Stack中的泛型數組建立錯誤的第二種方法是將屬性元素的類型從E []
更改成Object []
。 若是這樣作,會獲得一個不一樣的錯誤:設計
Stack.java:19: incompatible types found: Object, required: E E result = elements[--size]; ^
能夠經過將從數組中檢索到的元素轉換爲E
來將此錯誤更改成警告:
Stack.java:19: warning: [unchecked] unchecked cast found: Object, required: E E result = (E) elements[--size]; ^
由於E是不可具體化的類型,編譯器沒法在運行時檢查強制轉換。 再一次,你能夠很容易地向本身證實,不加限制的轉換是安全的,因此能夠適當地抑制警告。 根據條目 27的建議,咱們只在包含未經檢查的強制轉換的分配上抑制警告,而不是在整個pop
方法上:
// 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; }
兩種消除泛型數組建立的技術都有其追隨者。 第一個更可讀:數組被聲明爲E []
類型,清楚地代表它只包含E實例。 它也更簡潔:在一個典型的泛型類中,你從代碼中的許多點讀取數組; 第一種技術只須要一次轉換(建立數組的地方),而第二種技術每次讀取數組元素都須要單獨轉換。 所以,第一種技術是優選的而且在實踐中更經常使用。 可是,它確實會形成堆污染(heap pollution)(條目 32):數組的運行時類型與編譯時類型不匹配(除非E碰巧是Object)。 這使得一些程序員很是不安,他們選擇了第二種技術,儘管在這種狀況下堆的污染是無害的。
下面的程序演示了泛型Stack類的使用。 該程序以相反的順序打印其命令行參數,並將其轉換爲大寫。 對從堆棧彈出的元素調用String的toUpperCase
方法不須要顯式強制轉換,而自動生成的強制轉換將保證成功:
// Little program to exercise our generic 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()); }
上面的例子彷佛與條目 28相矛盾,條目 28中鼓勵使用列表優先於數組。 在泛型類型中使用列表並不老是可行或可取的。 Java自己生來並不支持列表,因此一些泛型類型(如ArrayList)必須在數組上實現。 其餘的泛型類型,好比HashMap,是爲了提升性能而實現的。
絕大多數泛型類型就像咱們的Stack示例同樣,它們的類型參數沒有限制:能夠建立一個Stack <Object>
,Stack <int []>
,Stack <List <String >>
或者其餘任何對象的Stack引用類型。 請注意,不能建立基本類型的堆棧:嘗試建立Stack<int>
或Stack<double>
將致使編譯時錯誤。 這是Java泛型類型系統的一個基本限制。 可使用基本類型的包裝類(條目 61)來解決這個限制。
有一些泛型類型限制了它們類型參數的容許值。 例如,考慮java.util.concurrent.DelayQueue
,它的聲明以下所示:
class DelayQueue<E extends Delayed> implements BlockingQueue<E>
類型參數列表(<E extends Delayed>
)要求實際的類型參數E
是java.util.concurrent.Delayed
的子類型。 這使得DelayQueue
實現及其客戶端能夠利用DelayQueue
元素上的Delayed
方法,而不須要顯式的轉換或ClassCastException異常的風險。 類型參數E被稱爲限定類型參數。 請注意,子類型關係被定義爲每一個類型都是本身的子類型[JLS,4.10],所以建立DelayQueue <Delayed>
是合法的。
總之,泛型類型比須要在客戶端代碼中強制轉換的類型更安全,更易於使用。 當你設計新的類型時,確保它們能夠在沒有這種強制轉換的狀況下使用。 這一般意味着使類型泛型化。 若是你有任何現有的類型,應該是泛型的但實際上卻不是,那麼把它們泛型化。 這使這些類型的新用戶的使用更容易,而不會破壞現有的客戶端(條目 26)。