Java 列舉 enum 詳解
阿新 • • 發佈:2020-11-25
> 本文部分摘自 On Java 8
## 列舉型別 Java5 中添加了一個 enum 關鍵字,通過 enum 關鍵字,我們可以將一組擁有具名的值的有限集合建立為一種新的型別,這些具名的值可以作為常規的程式元件使用,例如: ```java public enum Spiciness { NOT, MILD, MEDIUM, HOT, FLAMING } ``` 這裡建立了一個名為 Spiciness 的列舉型別,它有 5 個值。由於列舉型別的例項是常量,因此按照命名慣例,它們都用大寫字母表示(如果名稱中含有多個單詞,使用下劃線分隔) 要使用 enum,需要建立一個該型別的引用,然後將其賦值給某個例項: ```java public class SimpleEnumUse { public static void main(String[] args) { Spiciness howHot = Spiciness.MEDIUM; System.out.println(howHot); } } // 輸出:MEDIUM ``` 在 switch 中使用 enum,是 enum 提供的一項非常便利的功能。一般來說,在 switch 中只能使用整數值,而列舉例項天生就具備整數值的次序,並且可以通過 ordinal() 方法取得其次序,因此我們可以在 switch 語句中使用 enum 一般情況下我們必須使用 enum 型別來修飾一個 enum 例項,但是在 case 語句中卻不必如此。下面的例子使用 enum 構造了一個模擬紅綠燈狀態變化: ```java enum Signal { GREEN, YELLOW, RED, } public class TrafficLight { Signal color = Signal.RED; public void change() { switch(color) { case RED: color = Signal.GREEN; break; case GREEN: color = Signal.YELLOW; break; case YELLOW: color = Signal.RED; break; } } @Override public String toString() { return "The traffic light is " + color; } public static void main(String[] args) { TrafficLight t = new TrafficLight(); for(int i = 0; i < 7; i++) { System.out.println(t); t.change(); } } } ```
## EnumSet EnumSet 是一個用來操作 Enum 的集合,可以用來存放屬於同一列舉型別的列舉常量,其中元素存放的次序決定於 enum 例項定義時的次序。EnumSet 的設計初衷是為了替代傳統的基於 int 的“位標誌”。傳統的“位標誌”可以用來表示某種“開/關”資訊,不過,使用這種標誌,我們最終操作的只是一些 bit,而不是這些 bit 想要表達的概念,因此很容易寫出令人費解的程式碼 既然 EnumSet 要替代 bit 標誌,那麼它的效能應該要做到與使用 bit 一樣高效才對。EnumSet 的基礎是 long,一個 long 值有 64 位,一個 enum 例項只需一位 bit 表示其是否存在。 也就是說,在不超過一個 long 的表達能力的情況下,你的 EnumSet 可以應用於最多不超過 64 個元素的 enum。如果 enum 超過了 64 個元素,EnumSet 會在必要時增加一個 long EnumSet 的方法如下: | 方法 | 作用 | | ------------------------- | ------------------------------------------------------------ | | allOf(Class elementType) | 建立一個包含指定列舉類裡所有列舉值的 EnumSet 集合 | | complementOf(EnumSet e) | 建立一個元素型別與指定 EnumSet 裡元素型別相同的 EnumSet 集合,新 EnumSet 集合包含原 EnumSet 集合所不包含的的列舉值 | | copyOf(Collection c) | 使用一個普通集合來建立 EnumSet 集合 | | copyOf(EnumSet e) | 建立一個指定 EnumSet 具有相同元素型別、相同集合元素的 EnumSet 集合 | | noneOf(Class elementType) | 建立一個元素型別為指定列舉型別的空 EnumSet | | of(E first,E…rest) | 建立一個包含一個或多個列舉值的 EnumSet 集合,傳入的多個列舉值必須屬於同一個列舉類 | | range(E from,E to) | 建立一個包含從 from 列舉值到 to 列舉值範圍內所有列舉值的 EnumSet 集合 | 示例程式碼: ```java public enum AlarmPoints { STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY, KITCHEN } public class EnumSets { public static void main(String[] args) { EnumSet points = EnumSet.noneOf(AlarmPoints.class); points.add(BATHROOM); System.out.println(points); points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); System.out.println(points); points = EnumSet.allOf(AlarmPoints.class); points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); System.out.println(points); points.removeAll(EnumSet.range(OFFICE1, OFFICE4)); System.out.println(points); points = EnumSet.complementOf(points); System.out.println(points); } } ```
## EnumMap EnumMap 是一種特殊的 Map,它要求 key 必須為列舉型別,value 沒有限制,底層由雙陣列實現(一個存放 key,另一個存放 value),同時,當 value 為 null 時會特殊處理為一個 Object 物件,和 EnumSet 一樣,元素存放的次序決定於 enum 例項定義時的次序 ```java // key 型別 private final Class
## 常量特定方法 我們可以為 enum 定義一個或多個 abstract 方法,然後為每個 enum 例項實現該抽象方法 ```java public enum ConstantSpecificMethod { DATE_TIME { @Override String getInfo() { return DateFormat.getDateInstance().format(new Date()); } }, CLASSPATH { @Override String getInfo() { return System.getenv("CLASSPATH"); } }, VERSION { @Override 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()); } } ``` 在面向物件的程式設計中,不同的行為與不同的類關聯。通過常量相關的方法,每個 enum 例項可以具備自己獨特的行為,這似乎說明每個 enum 例項就像一個獨特的類。在上面的例子中,enum 例項似乎被當作其超類 ConstantSpecificMethod 來使用,在呼叫 getInfo() 方法時,體現出多型的行為 然而,enum 例項與類的相似之處也僅限於此了。我們並不能真的將 enum 例項作為一個型別來使用,因為每一個 enum 元素都是指定列舉型別的 static final 例項 除了 abstract 方法外,程式設計師還可以覆蓋普通方法 ```java public enum OverrideConstantSpecific { NUT, BOLT, WASHER { @Override void f() { System.out.println("Overridden method"); } }; void f() { System.out.println("default behavior"); } public static void main(String[] args) { for(OverrideConstantSpecific ocs : values()) { System.out.print(ocs + ": "); ocs.f(); } } } ```
## 多路分發 現在有一個數學表示式 Number.plus(Number),我們知道 Number 是各種數字物件的超類,假設有 a 和 b 兩個 Number 型別的物件,根據上述的表示式代入得 a.plus(b),但你現在只知道 a、b 屬於 Number 型別,具體是什麼數字你並不知道,有可能是整數、浮點數,根據不同的數字型別,執行數學操作後的結果應該也不一樣才對,怎麼讓它們正確地互動呢? 如果你瞭解 Java 多型就知道,Java 多型的實現依賴的是 Java 的動態繫結機制,在執行時發現物件的真實型別。但 Java 只支援單路分發,就是說如果要執行的操作包含不止一個型別未知的物件,那麼 Java 的動態繫結機制只能處理其中一個型別,a.plus(b) 涉及到兩個型別,自然無法解決我們的問題,所以我們必須自己來判定其他型別 解決問題的方法就是多路分發,上面的例子,由於只有兩個分發,一般稱為兩路分發。多型只能發生在方法呼叫時,所以,如果你想使用兩路分發,那麼就必須有兩個方法呼叫:第一個方法呼叫決定第一個未知型別,第二個方法呼叫決定第二個未知的型別。程式設計師必須設定好某種配置,以便一個方法呼叫能夠引出其他方法呼叫,從而在這個過程中處理多種型別 來看一個猜拳的例子: ```java package enums; public enum Outcome { WIN, LOSE, DRAW } // 猜拳的結果:勝、負、平手 ``` ```java package enums; import java.util.*; import static enums.Outcome.*; // 引入 enums,這樣就不用寫字首 Outcome 了 interface Item { Outcome compete(Item it); Outcome eval(Paper p); Outcome eval(Scissors s); Outcome eval(Rock r); } class Paper implements Item { @Override public Outcome compete(Item it) { return it.eval(this); } @Override public Outcome eval(Paper p) { return DRAW; } @Override public Outcome eval(Scissors s) { return WIN; } @Override public Outcome eval(Rock r) { return LOSE; } @Override public String toString() { return "Paper"; } } class Scissors implements Item { @Override public Outcome compete(Item it) { return it.eval(this); } @Override public Outcome eval(Paper p) { return LOSE; } @Override public Outcome eval(Scissors s) { return DRAW; } @Override public Outcome eval(Rock r) { return WIN; } @Override public String toString() { return "Scissors"; } } class Rock implements Item { @Override public Outcome compete(Item it) { return it.eval(this); } @Override public Outcome eval(Paper p) { return WIN; } @Override public Outcome eval(Scissors s) { return LOSE; } @Override public Outcome eval(Rock r) { return DRAW; } @Override public String toString() { return "Rock"; } } public class RoShamBo1 { static final int SIZE = 20; private static Random rand = new Random(47); public static Item newItem() { switch(rand.nextInt(3)) { default: case 0: return new Scissors(); case 1: return new Paper(); case 2: return new Rock(); } } public static void match(Item a, Item b) { System.out.println( a + " vs. " + b + ": " + a.compete(b)); } public static void main(String[] args) { for(int i = 0; i < SIZE; i++) match(newItem(), newItem()); } } ``` 上面就是多路分發的實現,它的好處在於避免判定多個物件的型別的冗餘程式碼,不過配置過程需要很多道工序。我們既然學習了列舉,自然可以考慮用列舉對程式碼進行優化 ```java public interface Competitor