Set 是一种不允许有重复元素存在的集合。enum 要求每个内部成员都是唯一的,因此看起来很像 Set,但是由于无法添加或移除元素,它并不如 Set 那么好用。于是 EnumSet 被引入,用来配合 enum 的使用,以替代传统的基于 int 的“位标识”用法。
这种标识通常被用来表明某些开-关状态信息,但最终你实际上是在操作各种位状态,而不是业务逻辑,所以非常容易写出难以理解的代码。
性能是 EnumSet 的设计目标之一,因为它需要和位标识竞争(位操作的性能通常远高于 HashSet)。其内部实现其实是一个被用作位数组的long型变量,所以它非常高效。
这样做的好处是,你现在拥有了一种更具表现力的方式来表达二进制特征的存在与否,而且无须担心性能。
EnumSet 中的元素必须来自某个枚举类型。下面是一个假想的例子,使用枚举来表示一栋大楼中警报感应器的安装位置:
AlarmPoints.java
public enum AlarmPoints {
STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
OFFICE4, BATHROOM, UTILITY, KITCHEN
}
EnumSets.java
import java.util.EnumSet;
import static enums.TEST0523.AlarmPoints.*;
public class EnumSets {
public static void main(String[] args) {
// Empty
EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);
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);
}
}
运行结果如下:
静态导入(static import)的作用是简化枚举常量的使用。方法名的自解释性相当好,你还可以在 JDK 的文档里找到完整的细节。如果你仔细看文档,会发现 of() 方法分别以可变参数和接收 3~5 个显式参数的方式进行了重载。
这体现了 EnumSet 对性能的关注,因为本来只需要一个接收可变参数的 of() 方法就能解决问题,但是这样会比通过显式参数的方式略为低效。因此,如果用 2~5 个参数来调用 of() 方法,实际起作用的是显式调用(速度稍快);如果用1个或5个以上的参数来调用,则会是可变参数的版本。
注意,如果用1个参数来调用,编译器并不会构建可变参数数组,因此这种情况下不会产生额外的开销。
EnumSet 是基于64位的 long 构建的,每个枚举实例需要占用1位来表达是否存在的状态,这意味着在单个 long 的支撑范围内,1个 Enumset 最多可支持包含64个元素的枚举类型。如果枚举类型中的元素超过了64个,会发生什么呢?
BigEnumSet.java
import java.util.EnumSet;
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);
}
}
运行结果如下:
显然 EnumSet 可以支持包含超过64个元素的枚举类型,所以我们可以推测,它在必要的时候会引入新的 long 型变量。