enumset 必须用静态工厂方法创建,因其是抽象类且无公开构造器;enummap 构造时不校验 key 类型,错误在运行时抛 classcastexception;二者序列化依赖枚举 values() 顺序,增删常量易致反序列化失败。

EnumSet 为什么必须用静态工厂方法创建
因为 EnumSet 是抽象类,没有公开构造器——直接 new EnumSet 会编译报错。它靠 EnumSet.noneOf()、EnumSet.allOf()、EnumSet.of() 这些静态工厂返回具体子类实例(RegularEnumSet 或 JumboEnumSet),底层自动选型。
常见错误现象:IDE 提示“Cannot instantiate the type EnumSet”,或手动 new 导致编译失败。
-
noneOf(Class<e>)</e>最安全,尤其在泛型推导模糊时显式传入枚举类 - 元素少(≤64)时用
RegularEnumSet(位向量实现,极省内存);超 64 个枚举常量才切到JumboEnumSet(用 long[] 数组) - 避免在循环里反复调用
of()创建新实例——它每次新建对象;高频场景改用noneOf()+add()复用
EnumMap 的 key 类型检查在运行时才触发
EnumMap 构造时只接受 Class<k></k>,不校验后续 put 的 key 是否属于该枚举——错误类型 key 会在第一次 put() 或 get() 时抛 ClassCastException,不是编译期报错。
使用场景:适合做“枚举 → 配置值”映射,比如 LogLevel → LogFormatter,但若混入非枚举 key(如字符串误传),程序跑一半才崩。
立即学习“Java免费学习笔记(深入)”;
- 构造时传的
Class必须是具体枚举类,不能是父类或接口 - key 为
null直接抛NullPointerException,和 HashMap 行为不同 - 内部用 Object[] 存 value,索引由枚举
ordinal()决定,所以遍历顺序 = 枚举声明顺序,无需额外排序
EnumSet 和 EnumMap 在序列化时的兼容性陷阱
它们都实现了 Serializable,但反序列化依赖枚举类的 values() 方法结果与序列化时一致——如果枚举增删了常量(尤其删掉中间项),ordinal() 偏移变化,EnumSet 可能漏掉元素,EnumMap 可能 key 错位甚至 ArrayIndexOutOfBoundsException。
性能影响:序列化体积比普通 Set/Map 小很多(EnumSet 底层是 long 值,EnumMap 是 Object[]),但牺牲了向后兼容性。
- 线上服务升级枚举类前,必须确认所有已序列化的数据已过期或迁移
- 跨服务传输时,避免直接序列化 EnumSet/EnumMap,改用
toList()或 JSON(如 Jackson 默认把枚举转 name 字符串) - 单元测试里故意删一个枚举常量再反序列化旧数据,能快速暴露这类问题
什么时候不该用 EnumSet/EnumMap
当枚举类型本身不稳定,或者集合需要支持动态扩展 key(比如未来要加新枚举值但老代码不能改),就别硬套。它们的优势全建立在“枚举集封闭且已知”这个前提上。
容易踩的坑:用 EnumSet.copyOf(otherSet) 传入非 EnumSet(如 HashSet),会抛 IllegalArgumentException;又或者误以为 EnumMap 支持 null key —— 它连 null 都不让你 put。
- 枚举常量超过 1024 个?
EnumSet的JumboEnumSet性能开始明显下降,考虑 BitSet 或普通 Set - 需要并发修改?它们都不自带线程安全,
Collections.synchronizedSet()包一层也治标不治本,不如换ConcurrentHashMap+ 枚举 key - 枚举类被代理(比如 Spring AOP 增强)?
EnumSet内部用getClass() == enumClass判定类型,代理类会导致匹配失败










