読者です 読者をやめる 読者になる 読者になる

【Effective Java】項目32:ビットフィールドの代わりに EnumSet を使用する

Java Effective Java

ビットフィールド

列挙型を集合として用いる場合、旧来は以下の方法が使われました。

public class Text {
    public static final int STYLE_BOLD   = 1 << 0; // 1
    public static final int STYLE_ITALIC = 1 << 1; // 2
    public static final int STYLE_UNDERLINE = 1 << 2; // 4
    public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8

    public void applyStyles(int styles) {
        ...
    }
}

// 利用
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

それぞれの定数を整数のビットフィールドを利用して表現し、集合操作をビット演算で実装する、というテクニックです。 ビットフィールド手法はビット演算を利用するため、多くの場合パフォーマンス上の利点があります。

しかし、この方法は項目30で説明した int enum パターンの短所のすべてを持っています。 加えて、整数として表現されている定数の集合をそのまま解釈するのは非常に困難です。

加えて、ビットフィールドで表現される要素すべてをイテレートする方法はありません。

EnumSet

java.util パッケージには enum 型定数の集合を表現するよりよい代替手段があります。 それは EnumSet です。

EnumSet は以下の利点を持ちます。

  • Set インタフェースを実装
    • 豊富な機能、安全性、他の Set との互換性
  • 内部的にはビットベクターを使って実装
    • パフォーマンス上、ビットフィールドと遜色ない
    • 特に 64 以下の Enum では long フィールド一つで表現

このように、EnumSet は通常の Set と同様であり、かつ、ビットフィールドのパフォーマンスを維持しています。 実際、集合操作に関しては、ビットフィールド手法と同じようなビット演算を行っていますが、それは実装の詳細として隠蔽されています。

上述したコードの EnumSet 利用バージョンは以下のようになります。

public class Text {
    public enum Style {
        BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
    }

    public void applyStyles(Set<Style> styles) {
       ...
    }
}

// 利用
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

applyStyles メソッドは EnumSet ではなく、インタフェースの Set として受け取っている点に注意してください。 実装型ではなくインタフェース型を受けつけるのは一般的には優れた方法です。

EnumSet の唯一の欠点は不変の EnumSet を生成できないことです。 Java 1.6 時点では Collections.unmodifiableSet で包む方法がありますが、シンプル性とパフォーマンス性でやや不便です。

感想

不変の EnumSet は Java8 でも追加されていないようだ。

一方、Google が作っている Guava には Sets.immutableEnumSet() がある。

http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Sets.html#immutableEnumSet%28java.lang.Iterable%29