EnumSet 是 Java 集合框架中专为枚举类型设计的 Set 实现,具有极高的性能和紧凑的内存占用。它的元素必须是同一枚举类型的实例,内部通过位向量实现,操作效率极高(如 add、remove、contains 等方法几乎是常量时间)。EnumSet 不允许包含 null 元素,且是有序的,元素顺序与枚举类中声明的顺序一致。它没有公共构造方法,通常通过静态方法(如 allOf()、of()、range() 等)创建实例。
EnumSet 是抽象类,其实际实现有两个,如下图:

其中:
RegularEnumSet 用于枚举常量数量较少(≤64)的情况,使用单个 long 存储
JumboEnumSet 用于枚举常量数量较多(>64)的情况,使用 long 数组存储
注意,这两个实现类会根据枚举类型的大小自动选择,无需手动指定。
allOf(Class<E> elementType) 创建包含指定枚举类型所有元素的 EnumSet
noneOf(Class<E> elementType) 创建指定枚举类型的空 EnumSet
of(E e) 创建包含指定单个枚举元素的 EnumSet
of(E e1, E e2) 创建包含指定两个枚举元素的 EnumSet(可支持最多 5 个参数)
range(E from, E to) 创建包含从 from 到 to 之间所有枚举元素的 EnumSet
copyOf(Collection<E> c) 复制集合 c 中的元素创建 EnumSet
add(E e) 添加元素
addAll(Collection<? extends E> c) 添加集合 c 中的所有元素
remove(E e) 移除元素
contains(E e) 判断是否包含元素
clear() 清空集合
retainAll(Collection<?> c) 仅保留与集合 c 共有的元素
下面通过一个简单例子介绍 EnumSet 的用法:
package com.hxstrive.java_collection.enumSet;
import java.util.EnumSet;
public class EnumSetDemo {
private static enum Day {
MONDAY("星期一", 1),
TUESDAY("星期二", 2),
WEDNESDAY("星期三", 3),
THURSDAY("星期四", 4),
FRIDAY("星期五", 5),
SATURDAY("星期六", 6),
SUNDAY("星期日", 7);
// 中文名称
private final String chineseName;
// 一周中的顺序(1表示周一,7表示周日)
private final int order;
/**
* 构造方法
* @param chineseName 中文名称
* @param order 顺序
*/
Day(String chineseName, int order) {
this.chineseName = chineseName;
this.order = order;
}
public String getChineseName() {
return chineseName;
}
public int getOrder() {
return order;
}
/**
* 判断是否为工作日(周一至周五)
* @return 如果是工作日返回true,否则返回false
*/
public boolean isWeekday() {
return this != SATURDAY && this != SUNDAY;
}
}
public static void main(String[] args) {
// 1. 创建包含所有星期的EnumSet
EnumSet<Day> allDays = EnumSet.allOf(Day.class);
System.out.println("所有星期: " + allDays);
//所有星期: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
// 2. 创建空的EnumSet
EnumSet<Day> emptyDays = EnumSet.noneOf(Day.class);
System.out.println("空集合: " + emptyDays);
//空集合: []
// 3. 创建包含指定单个元素的EnumSet
EnumSet<Day> singleDay = EnumSet.of(Day.FRIDAY);
System.out.println("只包含周五: " + singleDay);
//只包含周五: [FRIDAY]
// 4. 创建包含多个指定元素的EnumSet
EnumSet<Day> someDays = EnumSet.of(Day.MONDAY, Day.WEDNESDAY, Day.FRIDAY);
System.out.println("包含周一、周三、周五: " + someDays);
//包含周一、周三、周五: [MONDAY, WEDNESDAY, FRIDAY]
// 5. 创建包含一定范围元素的EnumSet(从周一到周五)
EnumSet<Day> workDays = EnumSet.range(Day.MONDAY, Day.FRIDAY);
System.out.println("工作日(周一到周五): " + workDays);
//工作日(周一到周五): [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
// 6. 创建包含周末的EnumSet(通过复制已有集合的补集)
EnumSet<Day> weekend = EnumSet.complementOf(workDays);
System.out.println("周末(周六和周日): " + weekend);
//周末(周六和周日): [SATURDAY, SUNDAY]
// 7. 集合操作:添加元素
EnumSet<Day> holidays = EnumSet.copyOf(weekend);
holidays.add(Day.FRIDAY); // 假设周五也放假
System.out.println("假期(周末+周五): " + holidays);
//假期(周末+周五): [FRIDAY, SATURDAY, SUNDAY]
// 8. 集合操作:移除元素
holidays.remove(Day.FRIDAY);
System.out.println("移除周五后的假期: " + holidays);
//移除周五后的假期: [SATURDAY, SUNDAY]
// 9. 集合操作:判断包含关系
boolean hasSaturday = weekend.contains(Day.SATURDAY);
System.out.println("周末包含周六吗?" + hasSaturday);
//周末包含周六吗?true
// 10. 集合操作:求交集
EnumSet<Day> intersection = EnumSet.copyOf(workDays);
intersection.retainAll(someDays); // 与周一、周三、周五的交集
System.out.println("工作日与{周一,周三,周五}的交集: " + intersection);
//工作日与{周一,周三,周五}的交集: [MONDAY, WEDNESDAY, FRIDAY]
// 11. 遍历EnumSet
System.out.println("\n遍历所有星期:");
for (Day day : allDays) {
System.out.println(day.getOrder() + ": " + day +
(day.isWeekday() ? " (工作日)" : " (周末)"));
}
//遍历所有星期:
//1: MONDAY (工作日)
//2: TUESDAY (工作日)
//3: WEDNESDAY (工作日)
//4: THURSDAY (工作日)
//5: FRIDAY (工作日)
//6: SATURDAY (周末)
//7: SUNDAY (周末)
}
}以下是 EnumSet 的部分源码,查看成员变量定义和构造方法定义:
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable {
// 枚举的类型信息
final Class<E> elementType;
// 枚举常量数组(缓存,避免多次调用Enum.values())
final Enum<?>[] universe;
// 私有构造方法,供子类调用
EnumSet(Class<E> elementType, Enum<?>[] universe) {
this.elementType = elementType;
this.universe = universe;
}
// ... 其他方法
}注意,EnumSet 本身是抽象类,实际上通过两个子类实现,如下:
RegularEnumSet 适用于枚举常量数量 ≤ 64 的情况,用 long 存储位向量。
JumboEnumSet 适用于枚举常量数量 > 64 的情况,用 long[] 存储位向量。
通过 noneOf(Class<E> elementType) 方法可以创建指定枚举类型的空 EnumSet,方法定义如下:
/**
* 创建一个指定枚举类型的空 EnumSet
* 该方法是 EnumSet 的核心静态工厂方法之一,负责根据枚举常量数量自动选择最优实现类。
*
* @param <E> 集合中元素的类型,必须是 Enum 的子类(即枚举类型)
* @param elementType 目标 EnumSet 要存储的枚举类型的 Class 对象
* @return 一个指定枚举类型的空 EnumSet 实例(具体是 RegularEnumSet 或 JumboEnumSet)
* @throws NullPointerException 如果传入的 elementType 为 null(无法确定枚举类型)
*/
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
// 1. 获取指定枚举类型的所有常量数组
// 该数组是 JDK 内部缓存的,避免每次调用 Enum.values() 创建新数组,提升性能
Enum<?>[] universe = getUniverse(elementType);
// 2. 校验 elementType 是否为合法枚举类型
// 若 universe 为 null,说明 elementType 不是枚举类,抛出类型转换异常
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
// 3. 根据枚举常量数量选择对应的 EnumSet 实现类
// 当常量数量 <= 64 时,使用 RegularEnumSet(基于单个 long 位向量存储,更高效)
// 当常量数量 > 64 时,使用 JumboEnumSet(基于 long 数组存储,支持更多元素)
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
/**
* 获取指定枚举类型 E 的所有常量组成的数组
*
* @param <E> 目标枚举类型,必须是 Enum 的子类
* @param elementType 要获取常量数组的枚举类型的 Class 对象
* @return 该枚举类型所有常量组成的数组(缓存的共享实例)
* 若 elementType 不是枚举类型,返回 null
*/
private static <E extends Enum<E>> E[] getUniverse(Class<E> elementType) {
// 1. 通过 SharedSecrets 获取 Java 语言内部访问器(JavaLangAccess)
// SharedSecrets 是 JDK 内部机制,用于在不暴露 public API 的情况下,
// 让核心类(如 EnumSet)访问JDK内部功能
//
// 2. 调用 JavaLangAccess 的 getEnumConstantsShared 方法,获取枚举类型的缓存常量数组
// 该方法返回的数组是枚举类初始化时创建并缓存的单例数组,所有调用者共享同一实例,
// 避免重复克隆数组的性能消耗
return SharedSecrets.getJavaLangAccess().getEnumConstantsShared(elementType);
}通过 allOf(Class<E> elementType) 方法,创建枚举类型 elementType 所有常量的 EnumSet 实例,相当于调用枚举的 values() 方法,将返回的枚举常量数组存入 EnumSet:
/**
* 创建一个包含指定枚举类型所有常量的 EnumSet
* 即集合内容与该枚举类型的完整常量列表完全一致(枚举有多少个常量,集合就包含多少个元素)
*
* @param <E> 集合中元素的类型,必须是 Enum 的子类(枚举类型)
* @param elementType 目标 EnumSet 要包含的枚举类型的 Class 对象
* @return 包含该枚举类型所有常量的 EnumSet 实例
* @throws NullPointerException 如果传入的 elementType 为 null(无法确定枚举类型及常量列表)
*/
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
// 1. 先通过 noneOf() 创建指定枚举类型的空 EnumSet
// 注意:noneOf() 会自动根据枚举常量数量选择 RegularEnumSet 或 JumboEnumSet
// 确保底层存储适配
EnumSet<E> result = noneOf(elementType);
// 2. 调用 EnumSet 的抽象方法 addAll(),向空集合中添加该枚举类型的所有常量
// 注意:addAll() 的具体实现由子类(RegularEnumSet 或 JumboEnumSet)提供
// RegularEnumSet 通过位运算将所有位设为 1,批量添加所有常量
// JumboEnumSet 通过遍历 long 数组,将所有数组元素的位设为 1,批量添加所有常量
result.addAll();
return result;
}通过 of(E e) 方法以及重载的 of() 方法,根据传递的初始值创建 EnumSet,重载方法签名如下:

源码定义如下:
/**
* 创建一个初始包含指定单个枚举元素的 EnumSet
*
* 注:本方法是一系列重载方法之一,分别支持初始化包含 1 到 5 个元素的 EnumSet。
* 第六个重载版本使用可变参数(varargs)特性,可创建包含任意数量元素的 EnumSet,
* 但相比非可变参数的重载版本,其运行速度可能较慢(因可变参数存在数组创建开销)。
*
* @param <E> 指定元素的类型及集合的元素类型(必须是枚举类型)
* @param e 集合初始要包含的单个元素
* @throws NullPointerException 如果传入的元素 e 为 null(EnumSet 不允许 null 元素)
* @return 初始包含指定元素的 EnumSet 实例
*/
public static <E extends Enum<E>> EnumSet<E> of(E e) {
// 1. 通过元素 e 的枚举类型(e.getDeclaringClass())创建空EnumSet
// getDeclaringClass() 返回该枚举常量所属的枚举类 Class 对象,确保集合类型与元素类型一致
EnumSet<E> result = noneOf(e.getDeclaringClass());
// 2. 向空集合中添加指定元素e
// add() 方法由具体子类(RegularEnumSet/JumboEnumSet)实现,通过位运算标记元素存在
result.add(e);
return result;
}通过 range(E from, E to) 方法创建一个初始元素包含指定两个端点(from~to)所定义范围内所有元素的EnumSet。
注意,返回的 EnumSet 会包含两个端点元素本身,若两个端点相同(from == to),则集合只包含该单个元素。但两个端点必须符合顺序要求(from 不能在枚举定义中位于 to 之后)。
源码定义如下:
/**
* 创建一个初始包含指定两个端点所定义范围内所有元素的 EnumSet
*
* @param <E> 参数元素及集合的元素类型(必须是枚举类型)
* @param from 范围的起始端点元素(包含在集合中)
* @param to 范围的结束端点元素(包含在集合中)
* @throws NullPointerException 如果 from 或 to 为 null(EnumSet 不允许 null元素)
* @throws IllegalArgumentException 如果 from 在枚举顺序中位于 to 之后(from.compareTo(to) > 0)
* @return 包含 from 到 to(含两端点)所有枚举元素的 EnumSet 实例
*/
public static <E extends Enum<E>> EnumSet<E> range(E from, E to) {
// 1. 校验范围的合法性:确保from不位于to之后
// 通过枚举的 compareTo 方法比较顺序(基于枚举常量的定义顺序)
if (from.compareTo(to) > 0)
throw new IllegalArgumentException(from + " > " + to);
// 2. 根据 from 所属的枚举类型创建空 EnumSet
EnumSet<E> result = noneOf(from.getDeclaringClass());
// 3. 调用抽象方法 addRange(),向空集合中添加 from 到 to 范围内的所有元素
// 具体实现由子类(RegularEnumSet 或 JumboEnumSet)提供:
// (1)利用枚举常量的 ordinal() 值连续性,通过位运算批量添加范围内元素
// (2)比逐个调用add()方法更高效,尤其对于大范围元素场景
result.addRange(from, to);
return result;
}上面介绍了如何通过 EnumSet 的 noneOf()、allOf()、of()、range() 方法去创建实例,但是将枚举元素添加到 EnumSet 均是通过抽象方法 addRange()、add()、addAll() 完成,由具体子类去实现,下面将介绍 RegularEnumSet 和 JumboEnumSet 具体实现类。
RegularEnumSet 是 Java 集合框架中 EnumSet 的一个私有实现类,专门用于处理元素数量不超过 64 个的枚举类型("常规大小" 的枚举)。它通过位运算实现高效的集合操作,是枚举集合的核心实现之一。
类定义如下:
class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
@java.io.Serial
private static final long serialVersionUID = 3411599620347842686L;
/**
* 此集合的位向量表示。第 2^k 位表示 universe[k] 存在于此集合中。
* 使用 long 类型的位运算来高效存储枚举元素,每个位对应一个枚举常量
* 例如,bit 0 对应枚举中的第一个元素,bit 1 对应第二个元素,以此类推
*/
private long elements = 0L;
/**
* 创建一个指定枚举类型和枚举常量数组的 RegularEnumSet
* @param elementType 枚举类型的 Class 对象
* @param universe 包含所有枚举常量的数组(枚举的完整值集合)
*/
RegularEnumSet(Class<E> elementType, Enum<?>[] universe) {
super(elementType, universe);
}
/**
* 添加从 from 到 to(包括两者)的所有枚举元素到集合中
* 使用位运算高效设置范围内的所有位。
*
* @param from 起始枚举元素(包含)
* @param to 结束枚举元素(包含)
*/
void addRange(E from, E to) {
// 位运算逻辑解析:
// 1. (from.ordinal() - to.ordinal() - 1) 计算需要移位的位数
// 2. -1L >>> [位数] 生成从0到指定位置的连续1的位掩码
// 3. << from.ordinal() 将生成的位掩码左移到起始位置
// 最终结果:from 到 to 之间的所有位都被设置为1
// 例如:将 2 ~ 6 之间的位设置为1,以 int 为例
// -1 => 1111 1111 1111 1111 1111 1111 1111 1111
// -1 >>> (2 - 6 -1) >>> -5 >>> -5%32 >>> 27
// -1 >>> 27 => 0000 0000 0000 0000 0000 0000 0001 1111
// << 2 => 0000 0000 0000 0000 0000 0000 0111 1100
elements = (-1L >>> (from.ordinal() - to.ordinal() - 1)) << from.ordinal();
}
/**
* 将所有可能的枚举元素添加到集合中(即包含枚举类型的所有常量)
*/
void addAll() {
// 如果枚举常量数组不为空
if (universe.length != 0)
// 生成一个前 universe.length 位都为 1 的位掩码
// 当 universe.length <= 64 时,-1L >>> -universe.length
// 等价于
// (1L << universe.length) - 1
elements = -1L >>> -universe.length;
}
//...省略...
}注意:
(1)ordinal() 是 Enum 类的一个内置方法,用于返回枚举常量在其枚举声明中的位置索引(从 0 开始)。
(2)在 Java 中,>>> 是无符号右移运算符,它的作用是将一个整数的二进制位向右移动指定的位数,且高位始终用 0 填充(无论原数是正数还是负数)。
当 >>> 移动的位数是负数时,Java 会对这个负数进行特殊处理:将移动位数对 32(对于 int 类型)或 64(对于 long 类型)取模,最终使用模运算的结果作为实际移动位数。
具体规则:
对于 int 类型(32 位)若移动位数为 -n,实际移动位数为 -n % 32。例如:a >>> -1 等价于 a >>> 31(因为 -1 % 32 = 31)。
对于 long 类型(64 位)若移动位数为 -n,实际移动位数为 -n % 64。例如:b >>> -2 等价于 b >>> 62(因为 -2 % 64 = 62)。
从集合删除元素的源码如下:
/**
* 从集合中移除指定的元素(如果该元素存在)
*
* @param e 要从集合中移除的元素(如果存在)
* @return 如果集合中包含该元素并成功移除,则返回 true;否则返回 false
*/
public boolean remove(Object e) {
// 枚举集合中不允许存在null元素,直接返回false
if (e == null)
return false;
// 获取元素的类对象,用于类型检查
Class<?> eClass = e.getClass();
// 检查元素类型是否与当前集合的枚举类型一致
// 若元素的类不是当前枚举类型,且其超类也不是当前枚举类型,则不是该集合中的元素
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false;
// 保存当前的元素位向量,用于后续判断是否发生变化
long oldElements = elements;
// 通过位运算移除元素,核心逻辑:
// 1. ((Enum<?>)e).ordinal() 获取该枚举元素的序号
// 2. 1L << 序号:生成只有该元素对应位为1的位掩码
// 3. ~运算:对上述位掩码取反,得到只有该元素对应位为0、其他位为1的掩码
// 4. elements & 上述结果:将 elements 中该元素对应的位设为 0(即移除该元素)
elements &= ~(1L << ((Enum<?>)e).ordinal());
// 若移除前后的位向量不同,说明成功移除了元素,返回 true;否则返回 false
return elements != oldElements;
}到这里,RegularEnumSet 中新增和删除的方法就介绍完了,更多方法实现读者自行阅读。
JumboEnumSet 是 Java 集合框架中 EnumSet 的另一个私有实现类,专门用于处理元素数量超过 64 个的枚举类型("大型" 枚举)。它与 RegularEnumSet 共同构成了枚举集合的完整实现,根据枚举常量的数量自动选择合适的实现类。
当枚举类型包含的常量数量 超过 64 个 时,EnumSet 的静态工厂方法(如 EnumSet.of()、EnumSet.allOf())会自动返回 JumboEnumSet 实例。这是因为 RegularEnumSet 基于 long 类型的位向量(仅能表示 64 位),无法处理更多元素。
JumboEnumSet 内部使用 long[] 数组作为位向量存储元素,数组中的每个 long 元素可表示 64 个枚举常量:
数组索引 i 对应的 long 值,负责存储序号范围为 [i×64, (i+1)×64 - 1] 的枚举常量。
若第 k 位为 1,表示集合包含对应序号的枚举元素。
类定义如下:
class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
@java.io.Serial
private static final long serialVersionUID = 334349849919042784L;
/**
* 此集合的位向量表示。数组中第j个元素的第i位表示
* universe[64*j + i]是否存在于该集合中
* 说明:使用 long 数组分段存储位向量,每个 long 处理64个枚举元素
*/
private long elements[];
/**
* 集合中元素的数量
*/
private int size = 0;
/**
* 创建指定枚举类型和枚举常量数组的 JumboEnumSet
*
* @param elementType 枚举类型的Class对象
* @param universe 包含所有枚举常量的完整数组
*/
JumboEnumSet(Class<E> elementType, Enum<?>[] universe) {
super(elementType, universe);
// 计算存储所有枚举元素所需的long数组长度
// (universe.length + 63) >>> 6 等价于 (universe.length + 63) / 64
// 确保数组长度能容纳所有枚举元素
elements = new long[(universe.length + 63) >>> 6];
}
/**
* 添加从 from 到 to(包括两者)的所有枚举元素到集合中
*
* @param from 起始枚举元素(包含)
* @param to 结束枚举元素(包含)
*/
void addRange(E from, E to) {
// 计算起始元素在long数组中的索引(ordinal / 64)
int fromIndex = from.ordinal() >>> 6;
// 计算结束元素在long数组中的索引(ordinal / 64)
int toIndex = to.ordinal() >>> 6;
// 如果起始和结束元素在同一个long数组元素中
if (fromIndex == toIndex) {
// 生成从from到to的连续位掩码并设置
elements[fromIndex] = (-1L >>> (from.ordinal() - to.ordinal() - 1))
<< from.ordinal();
} else {
// 处理起始元素所在的long:从from开始到该long末尾的所有位设为1
elements[fromIndex] = (-1L << from.ordinal());
// 处理中间的所有long:全部设为1(表示所有64位都有元素)
for (int i = fromIndex + 1; i < toIndex; i++)
elements[i] = -1;
// 处理结束元素所在的long:从开始到to的所有位设为1
elements[toIndex] = -1L >>> (63 - to.ordinal());
}
// 更新集合大小
size = to.ordinal() - from.ordinal() + 1;
}
/**
* 将所有可能的枚举元素添加到集合中(即包含枚举类型的所有常量)
*/
void addAll() {
// 先将所有long元素都设为-1(二进制全为1)
for (int i = 0; i < elements.length; i++)
elements[i] = -1;
// 对最后一个long元素进行调整,只保留有效位数(枚举元素实际数量可能不是64的整数倍)
elements[elements.length - 1] >>>= -universe.length;
// 更新集合大小为枚举元素总数
size = universe.length;
}
//...省略...
}从集合删除元素的源码如下:
/**
* 从集合中移除指定的元素(如果该元素存在)
*
* @param e 要从集合中移除的元素(如果存在)
* @return 如果集合中包含该元素并成功移除,则返回{@code true};否则返回{@code false}
*/
public boolean remove(Object e) {
// 枚举集合中不允许存在null元素,直接返回false
if (e == null)
return false;
// 获取元素的类对象,用于类型检查
Class<?> eClass = e.getClass();
// 检查元素类型是否与当前集合的枚举类型一致
// 若元素的类不是当前枚举类型,且其超类也不是当前枚举类型,则不是该集合中的元素
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false;
// 获取元素在枚举中的序号
int eOrdinal = ((Enum<?>)e).ordinal();
// 计算该元素在long数组中的索引(ordinal / 64)
int eWordNum = eOrdinal >>> 6;
// 保存当前的long值,用于后续判断是否发生变化
long oldElements = elements[eWordNum];
// 核心逻辑:
// 1. 1L << eOrdinal:生成只有该元素对应位为 1 的位掩码
// 2. ~运算:对上述位掩码取反
// 3. elements[eWordNum] & 上述结果:将该元素对应的位设为0(即移除该元素)
elements[eWordNum] &= ~(1L << eOrdinal);
// 判断是否成功移除元素
boolean result = (elements[eWordNum] != oldElements);
// 如果成功移除,更新集合大小
if (result)
size--;
return result;
}更多信息请参考 https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html API 文档。