第十九章、枚舉類型
概述:
關鍵字enum:可以將一組具名的值的有限集合創建為一種新的類型(Class),而這些具名的值可以作為常規的程序組件使用。
創建enum時,編譯器會為你自動生成一個相關類,此類自動extends java.lang.Enum類。
1. 基本enum特性
Enum類提供的功能如下:
values() 返回enum實例的數組,而且保持聲明的順序:
ordinal() 返回一個int值,這是每個enum實例在聲明時的次序,從0開始。
== 來比較enum實例,編譯器會自動提供equals和hashCode方法。
getDeclaringClass() 獲取其所屬的enum類。
name() 返回enum實例聲明時的名字,與使用toString()效果相同。
Enum.valueOf() 是在Enum中定義的static方法,他根據給定的名字返回相應的enum實例,如果不存在會拋出異常。
1.1 將靜態導入用于enum
使用static import能夠將enum實例的標識符代入當前的命名空間,所以無需再用enum類型來修飾enum實例。
唯一擔心的是使用靜態導入會不會導致代碼令人難以理解。
import static enumerated.Spiciness*;
1.2 向enum中添加新方法
除了不能繼承自一個enum之外,基本上可以將enum看做一個常規類。
也就是說,可以添加方法,甚至可以有main方法。
必須先定義enum實例,如果在實例之前定義任何方法或屬性,編譯時會報錯。
一旦enum的定義結束,編譯器就不允許在使用其構造器來創建任何實例了。
public enum OzWitch {
? ? // Instances must be defined first, before methods:
? ? WEST("Miss Gulch"),
? ? NORTH("Glinda"),
? ? EAST("Wicked"),
? ? SOUTH("Good");//必須在enum實例序列的最后添加一個分號。
? ? private String description;
? ? // Constructor must be package or private access:
? ? private OzWitch(String description) {
? ? ? ? this.description = description;
? ? }
? ? public String getDescription() {
? ? ? ? return description;
? ? }
? ? @Override
? ? public String toString() {}
? ? public static void main(String[] args) {
? ? ? ? for (OzWitch witch : OzWitch.values()) {
? ? ? ? ? ? print(witch + ": " + witch.getDescription());
? ? ? ? }
? ? }
}
2. switch語句中的enum
一般來說switch中只能使用整形值;
而枚舉類型天生就具備整形值的次序,并且可以通過ordinal()方法獲取其次序。
enum Signal {
? ? GREEN, YELLOW, RED,
}
public class TrafficLight {
? ? Signal color = Signal.RED;
? ? public void change() {
? ? ? ? switch (color) {
? ? ? ? ? ? case RED:
? ? ? ? ? ? case GREEN:
? ? ? ? ? ? case YELLOW:
? ? ? ? }
? ? }
}
3.values()的神秘之處
enum類都自動繼承自Enum類(編譯器實現),我們可以查看Enum中并沒有values()方法。利用反射機制查看究竟:
values()是由編譯器自動添加的static方法。同時創建Explore的過程中,編譯器還添加了valueOf()方法。不是Enum類已經有valueOf()方法了嗎,不過Enum中的ValueOf()方法需要兩個參數,這個新增方法只需一個參數。由于Set只存儲方法的名字,不考慮簽名,所以removeAll只剩下values。
由于values()方法有編譯器插入到enum定義中的static方法,所以enum向上轉型為Enum,那么values就不可訪問了,不過Class中有一個getEnumConstants方法,所以即便Enum接口中沒有vlaues方法,仍然可以通過Class對象取得所有enum實例:
enum Search {HITHER, YON}
public class UpcastEnum {
? ? public static void main(String[] args) {
? ? ? ? Search[] vals = Search.values();
? ? ? ? Enum e = Search.HITHER; // Upcast
? ? ? ? // e.values(); // No values() in Enum
? ? ? ? for (Enum en : e.getClass().getEnumConstants()) {
? ? ? ? ? ? System.out.println(en);
? ? ? ? }
? ? }
}
getEnumConstants() 獲取所有Enum對象的實例
5. 實現,而非繼承
不能extends:Java不支持多重繼承;并編譯器自動extends了Enum類了;
可以implements:創建一個新的enum,可以同時實現一個或多個接口。
enum CartoonCharacter implements Generator<CartoonCharacter> {
? ? SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;
? ? private Random rand = new Random(47);
? ? @Override
? ? public CartoonCharacter next() {
? ? ? ? return values()[rand.nextInt(values().length)];
? ? }
}
6. 隨機選取
7. 使用接口組織枚舉
子類化:implements是enum 子類化的唯一方法。
有時希望使用子類將一個enum中的元素進行分組。在一個接口內部創建實現該接口的枚舉,以此將元素分組。
public interface Food {
? enum Appetizer implements Food {
? ? SALAD, SOUP, SPRING_ROLLS;
? }
? enum MainCourse implements Food {
? ? LASAGNE, BURRITO, PAD_THAI,
? ? LENTILS, HUMMOUS, VINDALOO;
? }
? enum Dessert implements Food {
? ? TIRAMISU, GELATO, BLACK_FOREST_CAKE,
? ? FRUIT, CREME_CARAMEL;
? }
? enum Coffee implements Food {
? ? BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
? ? LATTE, CAPPUCCINO, TEA, HERB_TEA;
? }
}
public class TypeOfFood {
? public static void main(String[] args) {
? ? Food food = Appetizer.SALAD;
? ? food = MainCourse.LASAGNE;
? ? food = Dessert.GELATO;
? ? food = Coffee.CAPPUCCINO;
? }
}
8. 使用EnumSet替代位標志(BitSet)
Set:是一種集合不能添加重復元素。enum也要求其成員是唯一的。
EnumSet:Java SE5引入了EnumSet,是為了通過enum創建一種替代品,以替代傳統的基于int的“位標志”(BitSet)。這種標志可以用來表示某種開關信息,不過,使用這種標志,最終操作的只是一些bit。
EnumSet的性能:它在說明一個二進制位是否存在時,具有更好的表達能力,并且無需擔心性能。
EnumSet 包含的使用方法:
EnumSet.noneOf(AlarmPoints.class); 返回Empty set
EnumSet.complementOf() 用于創建包含與指定的Enum_Set類型相同的元素的EnumSet
EnumSet.of() 返回參數中添加的Enum元素集合
EnumSet.range() 返回參數中指定范圍的Enum元素
points.addAll() 添加所有Enum元素
points.removeAl() 移除參數中包含的Enum元素集合
研究EnumSet文檔,會發現of()方法被重載了很多次,不但為可變數量參數進行了重載,而且為接受2至5個顯式的參數的情況都進行了重載。這也從側面表現了EnumSet對性能的關注。
9. 使用EnumMap
EnumMap:是一種特殊的Map(Key-Value映射表),要求其中的鍵(key)必須來自于一個enum。
EnumMap的性能:由于enum本身的限制,所以EnumMap內部是數組實現。因此EnumMap速度很快,可以放心進行查找操作。
EnumMap的排序:與EnumSet一樣,enum實例(key)定義時的次序決定了其在EnumMap中的順序。
9.1 命令模式(GoF23之一)的具體實現
interface Command {
? ? void action();
}
enum AlarmPoints{
? ? STAIR,LOBBY,OFFICE,BATHROOM,UTILITY,KITCHEN
}
public class EnumMaps {
? ? public static void main(String[] args) {
? ? ? ? EnumMap<AlarmPoints, Command> em =
? ? ? ? ? ? new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
? ? ? ? em.put(KITCHEN, new Command() {
? ? ? ? ? ? public void action() {
? ? ? ? ? ? ? ? print("Kitchen fire!");
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? em.put(BATHROOM, new Command() {
? ? ? ? ? ? public void action() {
? ? ? ? ? ? ? ? print("Bathroom alert!");
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? for (Map.Entry<AlarmPoints, Command> e : em.entrySet()) {
? ? ? ? ? ? printnb(e.getKey() + ": ");
? ? ? ? ? ? e.getValue().action();
? ? ? ? }
? ? ? ? try { // If there's no value for a particular key:
? ? ? ? ? ? em.get(UTILITY).action();
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? print(e);
? ? ? ? }
? ? }
}
9.2 EnumMap 包含的使用方法:
put(k,v) 新增元素(k,v)
get(k) 返回參數k對應的value
entrySet() 返回<k,v>實例對象集合
Map.Entry<K,V>.getValue() 返回實例元素的value
10 常量相關的方法
常量相關的方法:constant-specific methods
多路分發:multiple dispatching
枚舉元素:在Enum中定義的元素都是Enum類中的各個實例對象,每個Enum元素都是一個Enum類型的staic final類型對象。
常量相關的方法的enum實現方式:enum 允許為每個enum實例(枚舉元素)編寫方法,從而為每個enum實例賦予各自不同的行為,從而為每個enum實例賦予各自不同的行為。
public enum ConstantSpecificMethod {
? ? DATE_TIME {
? ? ? ? String getInfo() {
? ? ? ? ? ? return? DateFormat.getDateInstance().format(new Date());
? ? ? ? }
? ? },
? ? CLASSPATH {
? ? ? ? String getInfo() {
? ? ? ? ? ? return System.getenv("CLASSPATH");
? ? ? ? }
? ? },
? ? VERSION {
? ? ? ? String getInfo() {
? ? ? ? ? ? return System.getProperty("java.version");
? ? ? ? }
? ? };
? ? abstract String getInfo();
? ? public static void main(String[] args) {
? ? ? ? for (ConstantSpecificMethod csm : values()) {
? ? ? ? ? ? System.out.println(csm.getInfo());
? ? ? ? }
? ? }
}
10.1 使用enum的職責鏈
職責鏈模式:(Chain of Responsibility,GoF23設計模式之一):程序員以多種不同的方式來解決一個問題,然后將它們鏈接在一起。當請求到來時,它遍歷這個鏈,直到這個鏈中的某個解決方案能夠處理此需求。
代碼示例 參照原文P607
10.2 使用enum的狀態機
狀態機模式:(GoF23設計模式之一):一個狀態機可以具有有限個特定的狀態,它通常根據輸入,從一個狀態轉移到下個狀態,不過也可能存在瞬時狀態(transient states),而一旦任務執行結束,狀態機會立即離開瞬時狀態。
瞬時狀態:(transient states):
代碼示例 參照原文P609
11 多路分發
單路分發:Java只支持單路分發:如果要執行的操作包含了不只一個類型未知的對象時,java的動態綁定機制只能處理其中一個的類型。
多路分發:含二路分發。
多路分發的幾種實現方式:
使用enum分發:
使用常量相關的方法分發
使用EnumMap分發
使用二維數組