[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 的特性

  1. Enum 類別是抽象類別,所以無法直接實例化。
  2. Enum 類別繼承了 Comparable 介面,所以 Enum 類別可以進行比較。主要是針對 Enum 類別的 ordinal 進行比較。 列舉在宣告時,會給予每個成員一個 name 和 ordinal,name 是成員的名稱,ordinal 是成員的編號,從 0 開始。 可以透過 name() 取得成員的名稱, 透過 ordinal() 取得成員的編號。
  3. Enum 類別實作了 Serializable 介面,所以 Enum 類別可以進行序列化。 為什麼我們要序列化 Enum 類別呢?因為我們會希望 Enum 類別可以透過序列化來使 Enum 包持單例,讓每個枚舉都只有一個實例。 通常我們會把 Enum 用來定義常數,這樣就可以確保程式中的常數都是唯一的。
  4. Enum 類別的建構子是 protected,所以無法直接建立 Enum 類別的實例。
  5. Enum 類別有一個 valueOf 方法,可以透過 name 取得 Enum 的實例。
  6. Enum 類別的 equals 方法被覆寫,且為 final,實作邏輯與 Object 類別的 equals 方法相同,都是比較記憶體位置。
  7. Enum 類別的 hashCode 方法被覆寫,且為 final,實作邏輯與 Object 類別的 hashCode 方法相同,都是回傳記憶體位置的 hash code。
  8. 定義 Enum 時,可以自定義 constructor,但是有個條件是不得為 public,且也不可於 constructor 中呼叫 super()。

Comparable 來這裡TODO看看。
Serializable 來這裡TODO看看。

來建立一些 Enum 範例吧

  • Priority1
    • Priority1 是一個 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());
        }
    }
}
  • Priority2
    • Priority2 是一個 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。
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