[Java] 列舉 Enum
目錄
Enum 的定義
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
private final int ordinal;
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String name() {
return name;
}
public int ordinal() {
return ordinal;
}
public String toString() {
return name;
}
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum other = (Enum)o;
Enum self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
public final Class<E> getDeclaringClass() {
Class clazz = getClass();
Class zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? clazz : zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() { }
}這也太落落長了吧,我們稍微聊解一些重要的部分。
Enum 的特性
Enum類別是抽象類別,所以無法直接實例化。Enum類別繼承了Comparable介面,所以 Enum 類別可以進行比較。主要是針對Enum類別的ordinal進行比較。 列舉在宣告時,會給予每個成員一個name和ordinal,name是成員的名稱,ordinal是成員的編號,從 0 開始。 可以透過name()取得成員的名稱, 透過ordinal()取得成員的編號。Enum類別實作了Serializable介面,所以 Enum 類別可以進行序列化。 為什麼我們要序列化 Enum 類別呢?因為我們會希望Enum類別可以透過序列化來使Enum包持單例,讓每個枚舉都只有一個實例。 通常我們會把Enum用來定義常數,這樣就可以確保程式中的常數都是唯一的。Enum類別的建構子是protected,所以無法直接建立Enum類別的實例。Enum類別有一個valueOf方法,可以透過name取得Enum的實例。Enum類別的equals方法被覆寫,且為final,實作邏輯與Object類別的equals方法相同,都是比較記憶體位置。Enum類別的hashCode方法被覆寫,且為final,實作邏輯與Object類別的hashCode方法相同,都是回傳記憶體位置的 hash code。- 定義
Enum時,可以自定義constructor,但是有個條件是不得為public,且也不可於constructor中呼叫super()。
來建立一些 Enum 範例吧
Priority1Priority1是一個 Enum,裡面有五個成員,分別是MAX、HIGH、MEDIUM、LOW、MIN,每個成員都有一個value,代表優先權值。Priority1有一個constructor,傳入一個int型態的參數,用來設定優先權值。Priority1有一個getValue方法,用來取得優先權值。Priority1有一個main方法,用來列印出每個成員的名稱和優先權值。
public enum Priority1 {
MAX(10), HIGH(8), MEDIUM(5), LOW(3), MIN(1); // 呼叫建構子
private final int value; // 儲存優先權值
Priority1(int value) { // 不為 public 的建構子
this.value = value;
}
public int getValue() { // 自定義方法
return value;
}
public static void main(String[] args) {
for (Priority1 priority : Priority1.values()) {
System.out.println(priority + " : " + priority.getValue());
}
}
}Priority2Priority2是一個 Enum,裡面有五個成員,分別是MAX、HIGH、MEDIUM、LOW、MIN,每個成員都有一個value,代表優先權值。Priority2有一個constructor,傳入一個int型態的參數,用來設定優先權值。Priority2有一個getValue方法,用來取得優先權值。Priority2有一個main方法,用來列印出每個成員的名稱和優先權值,並且覆寫toString方法,讓toString方法可以印出成員的名稱和優先權值。
public enum Priority2 {
MAX(10) {
public String toString() {
return format("最高(%2d)", value);
}
},
HIGH(8) {
public String toString() {
return format("高(%2d)", value);
}
},
MEDIUM(5) {
public String toString() {
return format("中(%2d)", value);
}
},
LOW(3) {
public String toString() {
return format("低(%2d)", value);
}
},
MIN(1) {
public String toString() {
return format("最低(%2d)", value);
}
};
protected int value; // 儲存優先權值
Priority2(int value) { // 不為 public 的建構子
this.value = value;
}
public int getValue() { // 自定義方法
return value;
}
public static void main(String[] args) {
for (Priority2 priority : Priority2.values()) {
System.out.println(priority);
}
}
}Action1- 在這個範例中,我們 implement 了
Command介面,並且在Action1中實作了Command介面的execute方法。再搭配switch來實作不同的行為。 Action1是一個 Enum,裡面有十個成員,分別是START、STOP、JUMP、SIT、RUN、WALK、SLEEP、EAT、DRINK、THINK。
- 在這個範例中,我們 implement 了
public enum Action1 implements Command{
START, STOP, JUMP, SIT, RUN, WALK, SLEEP, EAT, DRINK, THINK;
@Override
public void execute() {
switch (this) {
case START -> System.out.println("開始");
case STOP -> System.out.println("停止");
case JUMP -> System.out.println("跳躍");
case SIT -> System.out.println("坐下");
case RUN -> System.out.println("跑步");
case WALK -> System.out.println("走路");
case SLEEP -> System.out.println("睡覺");
case EAT -> System.out.println("吃飯");
case DRINK -> System.out.println("喝水");
case THINK -> System.out.println("思考");
default -> System.out.println("無法辨識");
}
}
public static void main(String[] args) {
for (Action1 action : Action1.values()) {
action.execute();
}
}
}Action2- 在這個範例中,我們使用了特別值類別本體(Value-specific class body)語法,這個語法讓我們把每個枚舉都定義成一個特別的類別本體,方便我們在每個枚舉中定義不同的行為。
- 在
Action1中,我們使用switch來實作不同的行為,好處是程式碼簡潔易懂,但如果我們需要添加或修改行為時,就需要修改Action1這個類別,這樣就違反了開放封閉原則(SOLID中的O-Open/Closed Principle)。 - 而在
Action2中。我們使用了特別值類別本體(Value-specific class body)語法,這個語法讓我們可以在每個枚舉中定義不同的行為,如果我們需要添加或修改行為時,只需要在枚舉中定義新的類別本體即可,這樣就符合了開放封閉原則(SOLID中的O-Open/Closed Principle)。
public enum Action2 implements Command{
// 特別值類別本體(Value-specific class body)語法
START {
public void execute() {
System.out.println("開始");
}
},
STOP {
public void execute() {
System.out.println("停止");
}
},
JUMP {
public void execute() {
System.out.println("跳躍");
}
},
SIT {
public void execute() {
System.out.println("坐下");
}
},
RUN {
public void execute() {
System.out.println("跑步");
}
},
WALK {
public void execute() {
System.out.println("走路");
}
},
SLEEP {
public void execute() {
System.out.println("睡覺");
}
},
EAT {
public void execute() {
System.out.println("吃飯");
}
},
DRINK {
public void execute() {
System.out.println("喝水");
}
},
THINK {
public void execute() {
System.out.println("思考");
}
},
UNKNOWN {
public void execute() {
System.out.println("無法辨識");
}
};
public static void main(String[] args) {
for (Action2 action : Action2.values()) {
action.execute();
}
}
}常見的 Enum 使用情境
- 表示一組固定的選項:例如,一個應用程式可能有多種狀態,你可以使用枚舉來表示這些狀態
public enum AppState {
STARTING,
RUNNING,
SHUTTING_DOWN,
STOPPED
}- 作為switch語句的參數:枚舉常數可以作為switch語句的參數,這樣可以確保只有有效的值可以被傳遞給switch語句。
// 如前述範例 Action1- 實現策略模式:每個枚舉常數都可以有自己的行為,這使得枚舉可以用來實現策略模式。
// 如前述範例 Action2,以下另外提供一個範例
public enum MathOperation {
ADD {
public int execute(int a, int b) { return a + b; }
},
SUBTRACT {
public int execute(int a, int b) { return a - b; }
},
MULTIPLY {
public int execute(int a, int b) { return a * b; }
},
DIVIDE {
public int execute(int a, int b) { return a / b; }
};
public abstract int execute(int a, int b);
}- 作為單例模式的實現方式:由於枚舉實例的創建和初始化都是由JVM保證的,因此,枚舉也可以用來實現單例模式。
另外,枚舉類型的實例是唯一的,所以它們是線程安全的。
如果你是使用類別搭配@Scope("singleton")來實現單例模式,那麼你還是需要自己處理多執行緒的問題。但是如果你使用枚舉來實現單例模式,那麼你就不需要擔心多執行緒的問題了。
public enum Singleton {
INSTANCE;
public void doSomething() {
// do something
}
}結論
- 總之,枚舉是一種特殊的類型,它可以用來表示一組固定的常數。枚舉在Java中是一個非常有用的功能,常常在各種情況下扮演輔助的腳色。
- 我把 code 就放在這裡囉!
Reference
- https://docs.oracle.com/javase%2F8%2Fdocs%2Fapi%2F%2F/java/lang/Enum.html
- Java SE 14 技術手冊 - 林信良 良葛格QQ