繼上一節 EnumMap 後,本節探討 EnumSet——專為枚舉類型設計的 Set 實現。不同於 HashSet (基於 HashMap) 和 TreeSet (基於 TreeMap),EnumSet 使用位向量實現,極致壓縮空間、極速位運算,是處理枚舉集合的「利器」。本文將覆蓋:基本用法、實戰場景、位向量原理、源碼級實現分析。
EnumSet 是抽象類,無公共構造器,通過靜態工廠方法獲取實例:
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
// 1. 空集合
Set<Day> weekend = EnumSet.noneOf(Day.class);
weekend.add(Day.SATURDAY);
weekend.add(Day.SUNDAY); // [SATURDAY, SUNDAY]
// 2. 全集合
Set<Day> allDays = EnumSet.allOf(Day.class); // 7 個元素
// 3. 範圍集合 (包含端點)
Set<Day> workDays = EnumSet.range(Day.MONDAY, Day.FRIDAY); // Mon-Fri
// 4. 補集
Set<Day> nonWorkDays = EnumSet.complementOf(workDays); // Sat, Sun
// 5. 顯式元素 (多重載優於可變參數性能)
Set<Day> mwf = EnumSet.of(Day.MONDAY, Day.WEDNESDAY, Day.FRIDAY);
// 6. 複製
Set<Day> copy = EnumSet.copyOf(mwf);
性能提示:of(E...) 可變參數會創建數組,of(E, E) 等固定重載避免數組分配,枚舉元素少時優先用固定重載。
問題:張三 (Mon, Tue, Wed, Fri)、李四 (Tue, Thu, Sat)、王五 (Tue, Thu)。回答:
class Worker {
String name;
Set<Day> availableDays; // EnumSet
// constructor, getters
}
Worker[] workers = {
new Worker("張三", EnumSet.of(Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY, Day.FRIDAY)),
new Worker("李四", EnumSet.of(Day.TUESDAY, Day.THURSDAY, Day.SATURDAY)),
new Worker("王五", EnumSet.of(Day.TUESDAY, Day.THURSDAY))
};
// 1. 無人值班 = 全集合 - 並集
Set<Day> none = EnumSet.allOf(Day.class);
for (Worker w : workers) none.removeAll(w.getAvailableDays());
// [SUNDAY]
// 2. 至少一人 = 並集
Set<Day> atLeastOne = EnumSet.noneOf(Day.class);
for (Worker w : workers) atLeastOne.addAll(w.getAvailableDays());
// [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]
// 3. 全員值班 = 交集
Set<Day> all = EnumSet.allOf(Day.class);
for (Worker w : workers) all.retainAll(w.getAvailableDays());
// [TUESDAY]
// 4. 周一周二都在 = containsAll
Set<Worker> mwfWorkers = new HashSet<>();
Set<Day> mwf = EnumSet.of(Day.MONDAY, Day.TUESDAY);
for (Worker w : workers)
if (w.getAvailableDays().containsAll(mwf)) mwfWorkers.add(w);
// [張三]
// 5. 至少兩人 = 統計頻次 (EnumMap 輔助)
Map<Day, Integer> count = new EnumMap<>(Day.class);
for (Worker w : workers)
for (Day d : w.getAvailableDays())
count.merge(d, 1, Integer::sum);
Set<Day> atLeastTwo = EnumSet.noneOf(Day.class);
for (Map.Entry<Day, Integer> e : count.entrySet())
if (e.getValue() >= 2) atLeastTwo.add(e.getKey());
// [TUESDAY, THURSDAY]
核心洞察:EnumSet 將「集合運算」映射為「位運算」,addAll=或、retainAll=與、removeAll=與非、complementOf=非,代碼極其簡潔高效。
枚舉值按聲明順序映射到 bit 位:
Day.MONDAY → bit 0 (最低位)
Day.TUESDAY → bit 1
...
Day.SUNDAY → bit 6
bit 7+ → 未使用 (置 0)
集合 {MONDAY, TUESDAY, WEDNESDAY, FRIDAY}
= 0b00010111 = 0x17 = 23 (十進制)

| 類別 | 存儲結構 | 適用枚舉數量 |
|---|---|---|
RegularEnumSet | long elements (單個 64 位) | ≤ 64 |
JumboEnumSet | long[] elements (數組) | > 64 |
工廠方法 noneOf 根據 universe.length 決定實例化哪個子類。
public boolean add(E e) {
typeCheck(e);
long old = elements;
elements |= (1L << ((Enum)e).ordinal()); // 設置對應 bit 為 1
return elements != old;
}
public boolean remove(Object e) {
if (e == null) return false;
// 類型檢查...
long old = elements;
elements &= ~(1L << ((Enum)e).ordinal()); // 設置對應 bit 為 0
return elements != old;
}
集合論視角:A - B = A ∩ B',~(1L<<ordinal) 即 B'。
public boolean contains(Object e) {
// 類型檢查...
return (elements & (1L << ((Enum)e).ordinal())) != 0;
}
public boolean containsAll(Collection<?> c) {
if (!(c instanceof RegularEnumSet)) return super.containsAll(c);
RegularEnumSet es = (RegularEnumSet) c;
if (es.elementType != elementType) return es.isEmpty();
return (es.elements & ~elements) == 0; // es 中有元素不在 this 中 → 非子集
}
public boolean retainAll(Collection<?> c) {
if (!(c instanceof RegularEnumSet)) return super.retainAll(c);
RegularEnumSet es = (RegularEnumSet) c;
if (es.elementType != elementType) { elements = 0; return true; }
long old = elements;
elements &= es.elements; // 交集
return elements != old;
}
void complement() {
if (universe.length != 0) {
elements = ~elements; // 全部取反
// 掩碼:保留低 universe.length 位,高位清 0
// -1L >>> -universe.length 等價於 (1L << universe.length) - 1
elements &= -1L >>> -universe.length;
}
}
移位陷阱:Java 移位操作只取右操作數低 6 位 (long 取低 6 位即模 64)。-universe.length 為負數,補碼高位全 1,>>> 無符號右移,實際等價於 64 - universe.length 位右移。
// 添加元素
int eOrdinal = e.ordinal();
int eWordNum = eOrdinal >>> 6; // / 64
long old = elements[eWordNum];
elements[eWordNum] |= (1L << eOrdinal); // 對應 long 內部位運算
// size++ 維護
其他操作邏輯同 RegularEnumSet,只是多一層數組索引定位。
EnumSet 展示了計算機科學中經典的位向量模式:
掌握位向量思維,能在處理「有限狀態集合」問題時,跳出「對象集合」思維定勢,寫出極致高效的代碼。