第十九章、枚舉類型
概述:
關(guān)鍵字enum:可以將一組具名的值的有限集合創(chuàng)建為一種新的類型(Class),而這些具名的值可以作為常規(guī)的程序組件使用。
創(chuàng)建enum時,編譯器會為你自動生成一個相關(guān)類,此類自動extends java.lang.Enum類。
1. 基本enum特性
Enum類提供的功能如下:
values() 返回enum實(shí)例的數(shù)組,而且保持聲明的順序:
ordinal() 返回一個int值,這是每個enum實(shí)例在聲明時的次序,從0開始。
== 來比較enum實(shí)例,編譯器會自動提供equals和hashCode方法。
getDeclaringClass() 獲取其所屬的enum類。
name() 返回enum實(shí)例聲明時的名字,與使用toString()效果相同。
Enum.valueOf() 是在Enum中定義的static方法,他根據(jù)給定的名字返回相應(yīng)的enum實(shí)例,如果不存在會拋出異常。
1.1 將靜態(tài)導(dǎo)入用于enum
使用static import能夠?qū)num實(shí)例的標(biāo)識符代入當(dāng)前的命名空間,所以無需再用enum類型來修飾enum實(shí)例。
唯一擔(dān)心的是使用靜態(tài)導(dǎo)入會不會導(dǎo)致代碼令人難以理解。
import static enumerated.Spiciness*;
1.2 向enum中添加新方法
除了不能繼承自一個enum之外,基本上可以將enum看做一個常規(guī)類。
也就是說,可以添加方法,甚至可以有main方法。
必須先定義enum實(shí)例,如果在實(shí)例之前定義任何方法或?qū)傩?,編譯時會報錯。
一旦enum的定義結(jié)束,編譯器就不允許在使用其構(gòu)造器來創(chuàng)建任何實(shí)例了。
public enum OzWitch {
? ? // Instances must be defined first, before methods:
? ? WEST("Miss Gulch"),
? ? NORTH("Glinda"),
? ? EAST("Wicked"),
? ? SOUTH("Good");//必須在enum實(shí)例序列的最后添加一個分號。
? ? 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類(編譯器實(shí)現(xiàn)),我們可以查看Enum中并沒有values()方法。利用反射機(jī)制查看究竟:
values()是由編譯器自動添加的static方法。同時創(chuàng)建Explore的過程中,編譯器還添加了valueOf()方法。不是Enum類已經(jīng)有valueOf()方法了嗎,不過Enum中的ValueOf()方法需要兩個參數(shù),這個新增方法只需一個參數(shù)。由于Set只存儲方法的名字,不考慮簽名,所以removeAll只剩下values。
由于values()方法有編譯器插入到enum定義中的static方法,所以enum向上轉(zhuǎn)型為Enum,那么values就不可訪問了,不過Class中有一個getEnumConstants方法,所以即便Enum接口中沒有vlaues方法,仍然可以通過Class對象取得所有enum實(shí)例:
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對象的實(shí)例
5. 實(shí)現(xiàn),而非繼承
不能extends:Java不支持多重繼承;并編譯器自動extends了Enum類了;
可以implements:創(chuàng)建一個新的enum,可以同時實(shí)現(xiàn)一個或多個接口。
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. 隨機(jī)選取
7. 使用接口組織枚舉
子類化:implements是enum 子類化的唯一方法。
有時希望使用子類將一個enum中的元素進(jìn)行分組。在一個接口內(nèi)部創(chuàng)建實(shí)現(xiàn)該接口的枚舉,以此將元素分組。
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替代位標(biāo)志(BitSet)
Set:是一種集合不能添加重復(fù)元素。enum也要求其成員是唯一的。
EnumSet:Java SE5引入了EnumSet,是為了通過enum創(chuàng)建一種替代品,以替代傳統(tǒng)的基于int的“位標(biāo)志”(BitSet)。這種標(biāo)志可以用來表示某種開關(guān)信息,不過,使用這種標(biāo)志,最終操作的只是一些bit。
EnumSet的性能:它在說明一個二進(jìn)制位是否存在時,具有更好的表達(dá)能力,并且無需擔(dān)心性能。
EnumSet 包含的使用方法:
EnumSet.noneOf(AlarmPoints.class); 返回Empty set
EnumSet.complementOf() 用于創(chuàng)建包含與指定的Enum_Set類型相同的元素的EnumSet
EnumSet.of() 返回參數(shù)中添加的Enum元素集合
EnumSet.range() 返回參數(shù)中指定范圍的Enum元素
points.addAll() 添加所有Enum元素
points.removeAl() 移除參數(shù)中包含的Enum元素集合
研究EnumSet文檔,會發(fā)現(xiàn)of()方法被重載了很多次,不但為可變數(shù)量參數(shù)進(jìn)行了重載,而且為接受2至5個顯式的參數(shù)的情況都進(jìn)行了重載。這也從側(cè)面表現(xiàn)了EnumSet對性能的關(guān)注。
9. 使用EnumMap
EnumMap:是一種特殊的Map(Key-Value映射表),要求其中的鍵(key)必須來自于一個enum。
EnumMap的性能:由于enum本身的限制,所以EnumMap內(nèi)部是數(shù)組實(shí)現(xiàn)。因此EnumMap速度很快,可以放心進(jìn)行查找操作。
EnumMap的排序:與EnumSet一樣,enum實(shí)例(key)定義時的次序決定了其在EnumMap中的順序。
9.1 命令模式(GoF23之一)的具體實(shí)現(xiàn)
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) 返回參數(shù)k對應(yīng)的value
entrySet() 返回<k,v>實(shí)例對象集合
Map.Entry<K,V>.getValue() 返回實(shí)例元素的value
10 常量相關(guān)的方法
常量相關(guān)的方法:constant-specific methods
多路分發(fā):multiple dispatching
枚舉元素:在Enum中定義的元素都是Enum類中的各個實(shí)例對象,每個Enum元素都是一個Enum類型的staic final類型對象。
常量相關(guān)的方法的enum實(shí)現(xiàn)方式:enum 允許為每個enum實(shí)例(枚舉元素)編寫方法,從而為每個enum實(shí)例賦予各自不同的行為,從而為每個enum實(shí)例賦予各自不同的行為。
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的職責(zé)鏈
職責(zé)鏈模式:(Chain of Responsibility,GoF23設(shè)計模式之一):程序員以多種不同的方式來解決一個問題,然后將它們鏈接在一起。當(dāng)請求到來時,它遍歷這個鏈,直到這個鏈中的某個解決方案能夠處理此需求。
代碼示例 參照原文P607
10.2 使用enum的狀態(tài)機(jī)
狀態(tài)機(jī)模式:(GoF23設(shè)計模式之一):一個狀態(tài)機(jī)可以具有有限個特定的狀態(tài),它通常根據(jù)輸入,從一個狀態(tài)轉(zhuǎn)移到下個狀態(tài),不過也可能存在瞬時狀態(tài)(transient states),而一旦任務(wù)執(zhí)行結(jié)束,狀態(tài)機(jī)會立即離開瞬時狀態(tài)。
瞬時狀態(tài):(transient states):
代碼示例 參照原文P609
11 多路分發(fā)
單路分發(fā):Java只支持單路分發(fā):如果要執(zhí)行的操作包含了不只一個類型未知的對象時,java的動態(tài)綁定機(jī)制只能處理其中一個的類型。
多路分發(fā):含二路分發(fā)。
多路分發(fā)的幾種實(shí)現(xiàn)方式:
使用enum分發(fā):
使用常量相關(guān)的方法分發(fā)
使用EnumMap分發(fā)
使用二維數(shù)組