枚举 enum:不只是几个常量
更新时间:2026-09-20。初学者以为枚举只是一组命名常量,实际上 enum 是一个有固定实例的完整类,可以带字段、方法甚至抽象方法,也是 Effective Java 推荐的单例实现方式。
一、本质:语法糖 + 固定实例
java
enum Status { RUNNING, STOPPED, FAILED }编译器把它处理成 final class Status extends Enum<Status>,RUNNING/STOPPED/FAILED 是三个 public static final 的本类实例,JVM 保证每个常量单例、且在类加载时初始化。
java
Status s = Status.RUNNING;
Status[] all = Status.values(); // [RUNNING, STOPPED, FAILED]
Status s2 = Status.valueOf("FAILED"); // 按名取,写错抛 IllegalArgumentException
s.name(); // "RUNNING"(建议比较用 name/toString 谨慎)
s.ordinal(); // 0 —— 位置序号,不要拿它持久化,重排就变
s == Status.RUNNING; // 枚举比较可直接用 ==(单例),也可 equals二、带字段、构造器与方法
java
enum PayType {
ALIPAY("支付宝", 1),
WECHAT("微信", 2); // 分号结束常量列表
private final String label;
private final int code;
PayType(String label, int code) { // 构造器默认 private,不能外部 new
this.label = label;
this.code = code;
}
public String getLabel() { return label; }
public int getCode() { return code; }
public static PayType ofCode(int code) {
for (PayType t : values()) if (t.code == code) return t;
throw new IllegalArgumentException("未知 code: " + code);
}
}
PayType.ALIPAY.getLabel(); // 支付宝三、抽象方法:每个常量给不同行为(策略枚举)
java
enum Operation {
ADD { @Override int apply(int a, int b) { return a + b; } },
SUB { @Override int apply(int a, int b) { return a - b; } };
abstract int apply(int a, int b); // 每个枚举常量各自实现
}
Operation.ADD.apply(1, 2); // 3,天然的策略模式,新增运算只要加常量
四、EnumSet / EnumMap 与单例
EnumSet.allOf(PayType.class):底层用 bit 向量,遍历/包含极快且内存省,需要「枚举集合」时优先于 HashSet。EnumMap<Status,List<Task>>:底层用数组按 ordinal 索引,键是枚举时比 HashMap 更快。- 枚举单例:
public enum Singleton { INSTANCE; ... }。JVM 保证实例唯一,天然防反射重复创建、防反序列化生成新对象,比「双检锁 + volatile」写法更省心(对比见 volatile 与 JMM)。
坑
- 用 ordinal 存库/跨系统:一旦在中间插入或调整枚举顺序,旧数据语义全错;持久化用自定义 code。
- valueOf 拼错抛异常:外部输入转枚举要先
EnumSet校验或自写安全的of方法。 - 枚举构造器里访问静态字段:静态字段可能尚未初始化(枚举常量在静态初始化早期创建),小心 NPE。
- 枚举不能 extends 别的类:它已隐式继承
java.lang.Enum,但可以 implements 接口。 - JSON 序列化默认用 name():Jackson/Gson 下要与前端约定好是用名称还是 code,必要时加
@JsonValue/@JsonCreator。
总结
enum 本质是继承 Enum、持有固定单例实例的 final 类,可定义私有构造器、字段与普通方法,还能用「每个常量实现抽象方法」写出极简策略模式;遍历用 values、按名取用 valueOf(非法名抛异常)、需要集合时选 EnumSet/EnumMap,业务码持久化用自定义 code 而非会随顺序变的 ordinal;由于 JVM 保证唯一且防反射/反序列化复制,单元素枚举也是最安全的单例实现。 单例在并发下的安全细节见 volatile 与 JMM。