Effective Javaを読むチャレンジ-項目32-

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

ビットフィールドとは何でしょうか。Wikipediaを参照してみました。

ビットフィールド (英: bit field) は、プログラミングにおいてブーリアン型のフラグをコンパクトなビットの並びとして格納する手法である。ビットフィールドの格納には、整数型を使用する。個々のフラグは、ビット単位で格納される。通常は、ソースコードで、個別のビットがフラグに対応する意味を付けられた、2の冪乗の定数が定義される。
引用元:https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%83%E3%83%88%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89

ビットフィールドはフラグの集合をコンパクトに扱う手法とのことです。通信系とか、用途はさまざまあります。あとはバイナリファイルのフォーマットととして使う・・・サンプルを、見ました・・・正直に言うとビットフィールドを仕事で使ったことはないです。見たこともないです。

それはともかくとして。本書によると列挙型の要素が主に集合で使用されるならば、各定数に異なる2の累乗を割り当てて、int enumパターンを使用するのが従来の方法ということです。こうしていたようです。

上記の例で言うと、文字スタイルのための定数をビットの並びとして定義しています。1bit目を太字、2bit目を斜体、というように定数が対応するビットの位置にくるように2のべき乗の整数を割り当てています。これらの定数を1つの集合、この場合は1つのint型の値としてまとめることができます。つまり、この方法では定数の集合をビットフィールドとして扱うためにint定数を用意し、各定数に対してビット演算を行います。例えば太字と斜体を適用したいというときには、こうします。

しかし、このビットフィールドはint enum定数の短所をすべてもっており、さらなる短所も持っているとあります。ご覧のように「STYLE_BOLD | STYLE_ITALIC」のビット演算をした結果は整数で扱われると「3」と表示されます。少ない桁ならまだしも、巨大な10進数の整数を頭の中で2進数に変換できる人はそうそういないでしょう。また、ビットフィールドで表現されるすべての要素をイテレートする方法もないです。

じゃあenum型を使うんですねということで、次のようなenumを考えてみます。

確かに定数はenumで表せました。では、この定数を集合として扱う時はどうすればいいでしょう。それがjava.util.EnumSetクラスだということです。

EnumSetはjava.util.Setインタフェースを実装していて、単一のenum型を要素にした集合を表せますし、Setインタフェースの豊富な機能や安全性を持ち、他のSet実装との相互運用性を提供しています。しかし、内部的には各EnumSetはビットベクターとして表現されています。

ビットベクターというのは、[1,0,0,1,1,0, ・・・ ]のように、1と0が並んだものです。EnumSetは、enum型の定数をビットの並びを定義したものとし、その各ビットが1であればEnumSetにセットする、というような使い方をします。逆に言い換えると、EnumSetに格納されているビットが1になっていると考えればいいということですね。int定数のように整数を扱わなくても、EnumSet内でのenum定数の有無を見ればビットの並びがすぐ分かります。本書の「保持するenum型が64個以下の要素を持っている場合、EnumSet全体は単一のlongで表現されている」という部分は、longが64bitなので各ビットを表すEnumSetの要素の個数も64個以下であれば、longのビット演算が使えるよということですね。

64個以下の要素であればパフォーマンスはビットフィールドと遜色ないようです。removeAllやretainAllなどの一括操作がビット演算で実装されているからだということです。実際、EnumSet(正確にはEnumSetのパッケージ内サブクラスであるRegularEnumSetクラスやJumboEnumSetクラス)では、ビット演算が使われています。

つまり、ビットフィールドの代わりにenum型を用意し、定数の集合を扱う場合はEnumSetを使えばいいということになります。ところが、最終的に本項目のサンプルはこうなっています。

EnumSetではなく、Setインタフェースをメソッドの引数にしています。本書ではこれは、実装型ではなくインタフェース型を受け付けるのは優れた実践で、それにより、他のSet実装を渡す普通でないクライアントの可能性を許し、これといって不利な点はないと言っています。引数に渡す前にEnumSet自体は作っているはずですから、EnumSet独自のofやら何やらは必要ないでしょうし、これは確かに。

あとはこのサンプルクラスのクライアントコードですね。こうなります。

ofメソッドは引数のenum定数を持ったEnumSetを生成します。こういう書き方と同じです。

あとはまとめの一段落で終わりです。

列挙型が集合の中で使用されるというだけで、それをビットフィールドで表現する理由がない

この項目は列挙型を集合で扱う場合にビットフィールドを使う必要はなくて、EnumSetが使えるよという話です。何バイト目にこういう値が入っていなくてはいけないといったビットフィールドを実現するためにEnumSetを使うか、というとまた別の話のように思います。

EnumSetの本当の1つの短所は、不変なEnumSetを生成できないこと

Java SE 8の時点で不変なEnumSetは用意されていないです。それまではEnumSetをCollections.unmodifiableSetで包むしかなく、その場合は簡潔性とパフォーマンスが損なわれるということです。

以上です。

広告
  • LINEで送る