《JAVA編程思想》學習筆記:第19章(枚舉)

第十九章、枚舉類型

概述:

關鍵字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分發

使用二維數組

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容