Java 5 引入 enum,不僅是類型安全的常量集合,更是功能豐富的類:可實現接口、定義方法、字段、構造器、實現狀態機、策略模式、責任鏈、多重分派等。本文系統梳理枚舉核心特性、values() 機制、EnumSet/EnumMap 高性能集合、常量特定方法、狀態機與分派模式實戰。
java.lang.Enum 的 final 類,不可被繼承。ordinal() 返回聲明順序(從 0 開始),name() 返回常量名。Serializable、Comparable(按 ordinal 排序)。
public enum Operation {
PLUS("+") { public double apply(double x, double y) { return x + y; } },
MINUS("-") { public double apply(double x, double y) { return x - y; } },
TIMES("*") { public double apply(double x, double y) { return x * y; } },
DIVIDE("/") { public double apply(double x, double y) { return x / y; } };
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
public String getSymbol() { return symbol; }
public abstract double apply(double x, double y); // 抽象方法強制每個常量實現
// 靜態工廠方法:從符號反查
private static final Map BY_SYMBOL =
Arrays.stream(values()).collect(toMap(Operation::getSymbol, o -> o));
public static Operation fromSymbol(String s) { return BY_SYMBOL.get(s); }
}
private,只能在枚舉內部調用。toString() 自定義顯示。
public void execute(Operation op, double x, double y) {
switch (op) { // case 中無需寫 Operation.PLUS
case PLUS: System.out.println(x + y); break;
case MINUS: System.out.println(x - y); break;
default: throw new IllegalArgumentException("Unknown op: " + op);
}
}
ordinal() 實現 tableswitch/lookupswitch,效率極高。return,編譯器要求有 default(防止未來新增常量漏處理)。values() 方法之謎values() 是編譯器插入的 static 方法,不在 Enum 父類中。Enum 後將丟失 values(),需通過 Class.getEnumConstants() 獲取。final,不可繼承。
public interface Food { enum Category { MEAT, VEGETABLE } }
public enum Meat implements Food.Category { BEEF, PORK, CHICKEN }
public enum Vegetable implements Food.Category { CARROT, POTATO, TOMATO }
Enum,Java 不支持多重繼承,故不可繼承其他類。EnumSet:位向量高性能集合long (≤64 元素) 或 long[] 位向量存儲,極其緊湊高效。int 位掩碼。EnumSet.allOf()、EnumSet.noneOf()、EnumSet.of()、EnumSet.range()、EnumSet.complementOf()。EnumMap:枚舉專用 MapHashMap 更快、更省內存,無哈希碰撞。
public enum PayChannel {
ALIPAY {
@Override public void pay(double amt) { System.out.println("支付寶支付 " + amt); }
@Override public String getName() { return "支付寶"; }
},
WECHAT {
@Override public void pay(double amt) { System.out.println("微信支付 " + amt); }
@Override public String getName() { return "微信"; }
};
public abstract void pay(double amt);
public abstract String getName();
}
if-else/switch,符合開閉原則。
public enum State {
IDLE {
@Override State next(Event e) {
return e == Event.START ? RUNNING : IDLE;
}
},
RUNNING {
@Override State next(Event e) {
return e == Event.PAUSE ? PAUSED : e == Event.STOP ? IDLE : RUNNING;
}
},
PAUSED {
@Override State next(Event e) {
return e == Event.RESUME ? RUNNING : e == Event.STOP ? IDLE : PAUSED;
}
};
public abstract State next(Event e);
}
public enum Event { START, PAUSE, RESUME, STOP }
// 使用
State s = State.IDLE;
s = s.next(Event.START); // RUNNING
if-else 散落業務代碼,狀態轉移邏輯集中在枚舉內部。Java 單分派(只根據 this 動態綁定),雙分派需兩次方法調用。枚舉輔助三種實現:
enumA.execute(enumB) 內部 switch(enumB),雙分派退化為單分派+switch。Map, Action> 查表,O(1) 查找,真正雙分派。Action[][] table,最簡潔最高效,但類型安全稍弱。
// EnumMap 雙分派示例
enum Shape { CIRCLE, RECT }
enum Color { RED, BLUE }
interface DrawAction { void draw(); }
EnumMap> table = new EnumMap<>(Shape.class);
for (Shape s : Shape.values()) {
EnumMap row = new EnumMap<>(Color.class);
for (Color c : Color.values()) row.put(c, () -> System.out.println(s + "-" + c));
table.put(s, row);
}
// 調用
table.get(Shape.CIRCLE).get(Color.RED).draw();