0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effecive Java 6.36:ビットフィールドの代わりに EnumSet を使う

Posted at

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 に持たせて変換するのがベストプラクティス。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?