enumset 比 hashset 快得多,因其底层用 long 或 long[] 位图表示,操作均为 o(1) 位运算且无哈希冲突;内存占用低、无 gc 压力,但仅支持同枚举类型、不可存 null、非线程安全。

EnumSet 为什么比 HashSet 快得多
因为 EnumSet 底层不存对象引用,而是用一个 long 或 long[] 做位图——每个枚举常量对应一个 bit 位。添加、删除、contains 全是位运算,O(1) 且无哈希冲突。
常见错误现象:new HashSet<color>().addAll(Arrays.asList(Color.RED, Color.BLUE))</color> 看似简洁,但对象开销大、GC 压力高、迭代慢;换成 EnumSet.of(Color.RED, Color.BLUE),内存占用直接降一个数量级。
- 只支持同一枚举类型,传入不同 enum 会编译报错(
ClassCastException在运行时几乎不会出现) - 不能存
null,否则抛NullPointerException - 非线程安全,多线程写入必须外加同步(比如
Collections.synchronizedSet()包一层)
of() 和 noneOf() 的参数陷阱
EnumSet.of() 有多个重载,但只接受「至少一个」枚举值;如果想初始化空集合,必须用 EnumSet.noneOf(),而不是 of() 传空数组或 null——后者会直接 NullPointerException。
使用场景:动态构造集合时容易踩坑。比如从配置读取字符串再转枚举,可能结果为空列表:
立即学习“Java免费学习笔记(深入)”;
String[] colors = getConfig("colors"); // 可能为 null 或空数组
// ❌ 错误写法:
// EnumSet.of(Stream.of(colors).map(Color::valueOf).toArray(Color[]::new)); // 空数组 → NPE
// ✅ 正确写法:
EnumSet<Color> set = EnumSet.noneOf(Color.class);
if (colors != null) {
Arrays.stream(colors).map(Color::valueOf).forEach(set::add);
}
complementOf() 的实际用途和边界条件
EnumSet.complementOf() 返回「全集减去给定集合」,但全集大小由传入的 Class<e></e> 决定——也就是该 enum 的 values().length。它不关心你传入的集合是否包含非法值(反正编译期就卡死了)。
性能影响:生成补集本身很快(位取反),但后续迭代会遍历所有枚举常量(哪怕只有 2 个被排除),所以别在 enum 成员上千时用它做“排除少数”的逻辑。
- 必须传非空、非 empty 的已有
EnumSet,否则NullPointerException - 补集结果仍是可变集合,修改它不影响原集合,也不影响其他同类型补集
- 如果 enum 类型未来新增常量,已有
complementOf()结果会自动包含它——这点和硬编码的of()列表行为不同
序列化和 JSON 库兼容性问题
EnumSet 实现了 Serializable,但默认序列化格式是 JVM 内部位图 + 枚举类名,跨版本或跨语言基本不可读。用 Jackson / Gson 直接序列化会得到空对象或异常。
常见错误现象:Spring Boot 接口返回 EnumSet,前端收到 {} 或 500 错误;日志里看到 java.lang.UnsupportedOperationException: null(Gson 默认不支持)。
- Jackson:需注册
SimpleModule+EnumSetSerializer,或改用List<myenum></myenum>传输 - Gson:手动注册
TypeAdapter,或用set.registerTypeAdapter(EnumSet.class, new EnumSetTypeAdapter()) - 最省事方案:对外暴露一律转成
List或Set接口,内部才用EnumSet
位运算的高效是以牺牲通用性换来的——它快,但只活在 Java 进程里;一旦要出圈,就得先脱掉那层 long 皮。










