💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
<!-- Using EnumSet Instead of Flags --> ## 使用 EnumSet 替代 Flags Set 是一种集合,只能向其中添加不重复的对象。当然,enum 也要求其成员都是唯一的,所以 enum 看起来也具有集合的行为。不过,由于不能从 enum 中删除或添加元素,所以它只能算是不太有用的集合。Java SE5 引入 EnumSet,是为了通过 enum 创建一种替代品,以替代传统的基于 int 的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些 bit,而不是这些 bit 想要表达的概念,因此很容易写出令人难以理解的代码。 EnumSet 的设计充分考虑到了速度因素,因为它必须与非常高效的 bit 标志相竞争(其操作与 HashSet 相比,非常地快),就其内部而言,它(可能)就是将一个 long 值作为比特向量,所以 EnumSet 非常快速高效。使用 EnumSet 的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。 EnumSet 中的元素必须来自一个 enum。下面的 enum 表示在一座大楼中,警报传感器的安放位置: ```java // enums/AlarmPoints.java package enums; public enum AlarmPoints { STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY, KITCHEN } ``` 然后,我们用 EnumSet 来跟踪报警器的状态: ```java // enums/EnumSets.java // Operations on EnumSets // {java enums.EnumSets} package enums; import java.util.*; import static enums.AlarmPoints.*; public class EnumSets { public static void main(String[] args) { EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class); // Empty points.add(BATHROOM); System.out.println(points); points.addAll( EnumSet.of(STAIR1, STAIR2, KITCHEN)); System.out.println(points); points = EnumSet.allOf(AlarmPoints.class); points.removeAll( EnumSet.of(STAIR1, STAIR2, KITCHEN)); System.out.println(points); points.removeAll( EnumSet.range(OFFICE1, OFFICE4)); System.out.println(points); points = EnumSet.complementOf(points); System.out.println(points); } } ``` 输出为: ```java [BATHROOM] [STAIR1, STAIR2, BATHROOM, KITCHEN] [LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY] [LOBBY, BATHROOM, UTILITY] [STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4, KITCHEN] ``` 使用 static import 可以简化 enum 常量的使用。EnumSet 的方法的名字都相当直观,你可以查阅 JDK 文档找到其完整详细的描述。如果仔细研究了 EnumSet 的文档,你还会发现 of() 方法被重载了很多次,不但为可变数量参数进行了重载,而且为接收 2 至 5 个显式的参数的情况都进行了重载。这也从侧面表现了 EnumSet 对性能的关注。因为,其实只使用单独的 of() 方法解决可变参数已经可以解决整个问题了,但是对比显式的参数,会有一点性能损失。采用现在这种设计,当你只使用 2 到 5 个参数调用 of() 方法时,你可以调用对应的重载过的方法(速度稍快一点),而当你使用一个参数或多过 5 个参数时,你调用的将是使用可变参数的 of() 方法。注意,如果你只使用一个参数,编译器并不会构造可变参数的数组,所以与调用只有一个参数的方法相比,也就不会有额外的性能损耗。 EnumSet 的基础是 long,一个 long 值有 64 位,而一个 enum 实例只需一位 bit 表示其是否存在。 也就是说,在不超过一个 long 的表达能力的情况下,你的 EnumSet 可以应用于最多不超过 64 个元素的 enum。如果 enum 超过了 64 个元素会发生什么呢? ```java // enums/BigEnumSet.java import java.util.*; public class BigEnumSet { enum Big { A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45, A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56, A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67, A68, A69, A70, A71, A72, A73, A74, A75 } public static void main(String[] args) { EnumSet<Big> bigEnumSet = EnumSet.allOf(Big.class); System.out.println(bigEnumSet); } } ``` 输出为: ```java [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45, A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56, A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67, A68, A69, A70, A71, A72, A73, A74, A75] ``` 显然,EnumSet 可以应用于多过 64 个元素的 enum,所以我猜测,Enum 会在必要的时候增加一个 long。