Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必不少人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到如今已經將近8年的時間,但隨着Java 6,7,8,甚至9的發佈,Java語言發生了深入的變化。
在這裏第一時間翻譯成中文版。供你們學習分享之用。java
在幾乎全部方面,枚舉類型都優於本書初版中描述的類型安全模式[Bloch01]。 從表面上看,一個例外涉及可擴展性,這在原始模式下是可能的,但不受語言結構支持。 換句話說,使用該模式,有可能使一個枚舉類型擴展爲另外一個; 使用語言功能特性,它不能這樣作。 這不是偶然的。 大多數狀況下,枚舉的可擴展性是一個糟糕的主意。 使人困惑的是,擴展類型的元素是基類型的實例,反之亦然。 枚舉基本類型及其擴展的全部元素沒有好的方法。 最後,可擴展性會使設計和實現的不少方面複雜化。安全
也就是說,對於可擴展枚舉類型至少有一個有說服力的用例,這就是操做碼( operation codes),也稱爲opcodes。 操做碼是枚舉類型,其元素表示某些機器上的操做,例如條目 34中的Operation
類型,它表示簡單計算器上的功能。 有時須要讓API的用戶提供他們本身的操做,從而有效地擴展API提供的操做集。app
幸運的是,使用枚舉類型有一個很好的方法來實現這種效果。基本思想是利用枚舉類型能夠經過爲opcode類型定義一個接口,並實現任意接口。例如,這裏是來自條目 34的Operation
類型的可擴展版本:ide
// Emulated extensible enum using an interface public interface Operation { double apply(double x, double y); } public enum BasicOperation implements Operation { PLUS("+") { public double apply(double x, double y) { return x + y; } }, MINUS("-") { public double apply(double x, double y) { return x - y; } }, TIMES("*") { public double apply(double x, double y) { return x * y; } }, DIVIDE("/") { public double apply(double x, double y) { return x / y; } }; private final String symbol; BasicOperation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; } }
雖然枚舉類型(BasicOperation
)不可擴展,但接口類型(Operation
)是能夠擴展的,而且它是用於表示API中的操做的接口類型。 你能夠定義另外一個實現此接口的枚舉類型,並使用此新類型的實例來代替基本類型。 例如,假設想要定義前面所示的操做類型的擴展,包括指數運算和餘數運算。 你所要作的就是編寫一個實現Operation
接口的枚舉類型:學習
// Emulated extension enum public enum ExtendedOperation implements Operation { EXP("^") { public double apply(double x, double y) { return Math.pow(x, y); } }, REMAINDER("%") { public double apply(double x, double y) { return x % y; } }; private final String symbol; ExtendedOperation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; } }
只要API編寫爲接口類型(Operation
),而不是實現(BasicOperation
),如今就能夠在任何可使用基本操做的地方使用新操做。請注意,沒必要在枚舉中聲明apply
抽象方法,就像您在具備實例特定方法實現的非擴展枚舉中所作的那樣(第162頁)。 這是由於抽象方法(apply
)是接口(Operation
)的成員。測試
不只能夠在任何須要「基本枚舉」的地方傳遞「擴展枚舉」的單個實例,並且還能夠傳入整個擴展枚舉類型,並使用其元素。 例如,這裏是第163頁上的一個測試程序版本,它執行以前定義的全部擴展操做:this
public static void main(String[] args) { double x = Double.parseDouble(args[0]); double y = Double.parseDouble(args[1]); test(ExtendedOperation.class, x, y); } private static <T extends Enum<T> & Operation> void test( Class<T> opEnumType, double x, double y) { for (Operation op : opEnumType.getEnumConstants()) System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); }
注意,擴展的操做類型的類字面文字(ExtendedOperation.class
)從main
方法裏傳遞給了test
方法,用來描述擴展操做的集合。這個類的字面文字用做限定的類型令牌(條目 33)。opEnumType
參數中複雜的聲明(<T extends Enum<T> & Operation> Class<T>
)確保了Class對象既是枚舉又是Operation
的子類,這正是遍歷元素和執行每一個元素相關聯的操做時所須要的。命令行
第二種方式是傳遞一個Collection<? extends Operation>
,這是一個限定通配符類型(條目 31),而不是傳遞了一個class對象:翻譯
public static void main(String[] args) { double x = Double.parseDouble(args[0]); double y = Double.parseDouble(args[1]); test(Arrays.asList(ExtendedOperation.values()), x, y); } private static void test(Collection<? extends Operation> opSet, double x, double y) { for (Operation op : opSet) System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); }
生成的代碼稍微不那麼複雜,tes
t方法靈活一點:它容許調用者將多個實現類型的操做組合在一塊兒。另外一方面,也放棄了在指定操做上使用EnumSe
t(條目 36)和EnumMap
(條目 37)的能力。設計
上面的兩個程序在運行命令行輸入參數4和2時生成如下輸出:
4.000000 ^ 2.000000 = 16.000000 4.000000 % 2.000000 = 0.000000
使用接口來模擬可擴展枚舉的一個小缺點是,實現不能從一個枚舉類型繼承到另外一個枚舉類型。若是實現代碼不依賴於任何狀態,則可使用默認實現(條目 20)將其放置在接口中。在咱們的Operation
示例中,存儲和檢索與操做關聯的符號的邏輯必須在BasicOperation
和ExtendedOperation
中重複。在這種狀況下,這並不重要,由於不多的代碼是冗餘的。若是有更多的共享功能,能夠將其封裝在輔助類或靜態輔助方法中,以消除代碼冗餘。
該條目中描述的模式在Java類庫中有所使用。例如,java.nio.file.LinkOption
枚舉類型實現了CopyOption
和OpenOption
接口。
總之,雖然不能編寫可擴展的枚舉類型,可是你能夠編寫一個接口來配合實現接口的基本的枚舉類型,來對它進行模擬。這容許客戶端編寫本身的枚舉(或其它類型)來實現接口。若是API是根據接口編寫的,那麼在任何使用基本枚舉類型實例的地方,均可以使用這些枚舉類型實例。