6.36:ビットフィールドの代わりに EnumSet を使う
要点
ビットフィールド(int のビットマスク)でフラグを表すより、enum + EnumSet を使うほうが型安全・可読性・保守性が高く、なおかつ内部実装はビット集合で高速なので実際の性能も良い — つまりほとんどの場合 EnumSet がベター。
なぜ EnumSet が良いのか
- 型安全: enum 以外の値を入れられない(間違いがコンパイル時に減る)。
- 可読性: EnumSet.of(Permission.READ, Permission.WRITE) や perms.contains(Permission.EXECUTE) は直感的。ビット演算 flags & 0x04 より読みやすい。
- API が自然: 集合操作(追加・削除・和・積・補集合)がメソッドで直感的に書ける。
- 高速: EnumSet の内部実装は enum の ordinal() を使ったビットベクトルで最適化されており、ビットマスクと同等かそれ以上の高速性。
- 標準ユーティリティ: EnumSet.noneOf(), allOf(), of(...), complementOf(), range() 等が用意されている。
悪い例
古いビットマスク
public class PermissionFlags {
public static final int READ = 1 << 0; // 0b001
public static final int WRITE = 1 << 1; // 0b010
public static final int EXECUTE = 1 << 2; // 0b100
}
int flags = PermissionFlags.READ | PermissionFlags.WRITE;
if ((flags & PermissionFlags.EXECUTE) != 0) { ... } // なんのビットか直感的でない
- マジックナンバーやビット位置を間違えるリスクがある。可読性が低い。
良い例
Enum + EnumSet
public enum Permission { READ, WRITE, EXECUTE }
EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.EXECUTE)) { ... }
perms.add(Permission.EXECUTE);
perms.remove(Permission.WRITE);
EnumSet<Permission> all = EnumSet.allOf(Permission.class);
EnumSet<Permission> none = EnumSet.noneOf(Permission.class);
- 意味が明確で間違いに強い。集合操作が分かりやすい。
既存のビットマスクと互換させる方法
既存コードと互換を持たせたいときは、ordinal() を直接永続化に使わない点に注意しつつ変換ユーティリティを用意する方法が良い。
1. 単純 — ordinal ベース(注意点あり)
// ビットマスク -> EnumSet(ordinal を使う)
EnumSet<Permission> fromMask(int mask) {
EnumSet<Permission> s = EnumSet.noneOf(Permission.class);
for (Permission p: Permission.values()) {
if ((mask & (1 << p.ordinal())) != 0) s.add(p);
}
return s;
}
// EnumSet -> ビットマスク
int toMask(EnumSet<Permission> s) {
int mask = 0;
for (Permission p: s) mask |= (1 << p.ordinal());
return mask;
}
注意:
ordinal() に依存すると enum の並べ替えで互換性が壊れる — 永続化・プロトコル用途なら下の明示コードパターンを使う。
2. 明示的ビットコードを enum に持たせる(永続化向け) 【推奨】
public enum Permission {
READ(1<<0), WRITE(1<<1), EXECUTE(1<<2);
private final int mask;
Permission(int mask) { this.mask = mask; }
public int mask() { return mask; }
public static EnumSet<Permission> fromMask(int mask) {
EnumSet<Permission> s = EnumSet.noneOf(Permission.class);
for (Permission p : values()) if ((mask & p.mask) != 0) s.add(p);
return s;
}
public static int toMask(EnumSet<Permission> set) {
int m = 0;
for (Permission p : set) m |= p.mask;
return m;
}
}
- 明示的な mask を持てば、enum の宣言順を変えても互換性を保持できる。
まとめ
- 日常的なフラグ管理は EnumSet を第一選択に:読みやすさ・安全性・性能 の三拍子が揃っている。
- 既存のビットマスクと併用する場合は、ordinal() に依存しないように明示的マスクフィールドを enum に持たせて変換するのがベストプラクティス。