《Effective Java》學習筆記五——列舉和註解
列舉型別是指由一組固定的常量組成合法值的型別,例如一年中的季節、太陽系中的行星或者一副牌中的花色。
int列舉模式、String列舉模式都是不可取的。
Java的列舉本質上是int值。
Java列舉型別背後的基本想法非常簡單:它們就是通過公有的靜態final域為每個列舉常量匯出例項的類。因為沒有可以訪問的構造器,列舉型別是真正的final。因為客戶端既不能建立列舉型別的例項,也不能對它進行擴充套件,因此很可能沒有例項,而只有宣告過的列舉常量。換句話說,列舉型別是例項受控的。它們是單例的泛型化,本質上是單元素的列舉。列舉型別為型別安全的列舉模式提供了語言方面的支援。
為了將資料與列舉常量關聯起來,得宣告例項域,並編寫一個帶有資料並將資料儲存在域中的構造器。列舉天生就是不可變的,因此所有的域都應該為final的。
與列舉常量關聯的有些行為,可能只需要用在定義了列舉的類或者包中。這種行為最好被實現成私有的或者包級私有的方法。
如果一個列舉具有普遍適用性,它就應該成為一個頂層類;如果它只是被用在一個特定的頂層類中,它就應該成為該頂層類的一個成員類。
有時需要將本質上不同的行為與每個常量關聯起來。有一種方法是通過啟用列舉的值來實現;一種更好的方法可以將不同的行為與每個列舉常量關聯起來:在列舉型別中宣告一個抽象的apply方法,並在特定於常量的類主體中,用具體的方法覆蓋每個常量的抽象apply方法。這種方法被稱作特定於常量的方法實現。
特定於常量的方法實現可以與特定於常量的資料結合起來。
列舉型別有一個自動產生的valueOf(String)方法,它將常量的名字轉變成常量本身。
用例項域代替序數
許多列舉天生就與一個單獨的int值相關聯。所有的列舉都有一個ordinal方法,它返回每個列舉常量在型別中的數字位置。
永遠不要根據列舉的序數匯出與它關聯的值,而是要將它儲存在一個例項域中。
用EnumSet代替位域
如果一個列舉型別的元素主要用在集合中,一般就使用int列舉模式,將2的不同倍數賦予每個常量。這種表示法讓你用OR位運算將幾個常量合併到一個集合中,稱作位域。
位域是指資訊在儲存時,並不需要佔用一個完整的位元組,而只需佔幾個或一個二進位制位。
例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進位即可。
java.util包提供了EnumSet類來有效地表示從單個列舉型別中提取的多個值的多個集合。這個類實現Set介面,提供了豐富的功能,型別安全性,以及可以從任何其他Set實現中得到的互用性。但是在內部具體的實現上,每個EnumSet內容都表示為位向量。如果底層的列舉型別有64個或者更少的元素——大多數如此。整個EnumSet就用單個long來表示,因此它的效能比的上位域的效能。
用EnumMap代替序數索引
有一種非常快速的Map實現專門用於列舉鍵,稱作java.util.EnumMap。EnumMap在執行速度方面之所以能與通過序數索引的陣列相媲美,是因為EnumMap在內部使用了這種陣列。
最好不要用序數來索引陣列,而要使用EnumMap。如果你所表示的這種關係是多維的,就使用EnumMap<…, EnumMap<…>>。
用介面模擬可伸縮的列舉
雖然無法編寫可擴充套件的列舉型別,卻可以通過編寫介面以及實現該介面的基礎列舉型別,對它進行模擬。這樣允許客戶端編寫自己的列舉來實現介面。如果API是根據介面編寫的,那麼在可以使用基礎列舉型別的任何地方,也都可以使用這些列舉。
註解優先於命名模式
Java1.5 發行版本之前,一般使用命名模式表明有些程式元素需要通過某種工具或者框架進行特殊處理。例如,JUnit測試框架原本要求它的使用者一定要用test作為測試方法名稱的開頭。這種方法有幾個缺點:
- 文字拼寫錯誤會導致失敗,且沒有任何提示;
- 無法確保它們只用於相應的程式元素上;
- 它們沒有提供將引數值與程式元素關聯起來的好方法。
所有的程式設計師都應該使用Java平臺所提供的預定義的註解型別。還要考慮使用IDE或者靜態分析工具所提供的任何註解。這種註解可以提升由這些工具所提供的診斷資訊的質量。但是要注意這些註解還沒有標準化。
堅持使用Override註解
這個註解只能用在方法宣告中,它表示被註解的方法宣告覆蓋了超型別中的一個宣告。如果堅持使用這個註解,可以防止一大類的非法錯誤。
應該在你想要覆蓋超類宣告的每個方法宣告中使用Override註解。
有一個例外,在具體的類中,不必標註你確信覆蓋了抽象方法宣告的方法(雖然這麼做也沒有什麼壞處)。
用標記介面定義型別
標記介面是沒有包含方法宣告的介面,而只是指明或者標明一個類實現了具有某種屬性的介面。例如,考慮Serializable介面,通過實現這個介面,類表明它的例項可以被寫到ObjectOutputStream(或者“被序列化”)。
標記介面定義的型別是由被標記類的例項實現的;標記註解則沒有定義這樣的型別。這個型別允許你在編譯時捕捉在使用標記註解的情況下要到執行時才能捕捉到的錯誤。
標記介面勝過標記註解的另一個優點是,它們可以被更加精確地進行鎖定。
標記註解勝過標機介面的最大優點在與,它可以通過預設的方式新增一個或者多個註解型別元素,給已被使用的註解型別新增更多的資訊。標記註解的另一個優點在於,它們是更大的註解機制的一部分。因此,標記註解在那些支援註解作為程式設計元素之一的框架中同樣具有一致性。
如果想要定義一個任何新方法都不會與之關聯的型別,標記介面就是最好的選擇。如果想要標記程式元素而非類和介面,考慮到未來可能要給標記新增更多的資訊,或者標記要適合於已經廣泛使用了註解型別的框架,那麼標記註解就是正確的選擇。如果你發現自己在編寫的目標為ElementType.TYPE的標記註解型別,就要花點時間考慮清楚,它是否真的應該為註解型別,想想標記介面是否會更加合適呢。