Effective Javaを読むチャレンジ-項目33 その1-

こういうブログは続けることが何より難しいですね。偉大な先達や技術系ニュースサイトの人たちはすごいなあ、としみじみ思います。頑張ります。

項目33 序数インデックスの代わりにEnumMapを使用する

まずは冒頭、時には配列へのインデックスとしてordinalメソッドを使用しているコードを見かけるかもしれません、とあります。enum型の序数は0から始まる整数なので、配列のインデックスとして使おうと思えば使えます。

では、どういった状況でそういった使い方をしたくなるのかというと、例えばenumを何かのグループ名の定数として定義したとして、そのグループ情報を持ったあるデータ集合を、そのグループに分類したい場合です。本書ではハーブの種類をenumで定義、庭園の植物を配列で表し、その庭園の植物をハーブの種類ごとに分類する例が載っています。

その方法として、種類ごとに1つのセットの合計3つのセットを生成し、庭園を回って、個々のハーブを適切なセットに入れるとあります。セットに入れるのは、今回は庭園の植物の名前をハーブの種類ごとに振り分けたいからですね、重複している植物の名前は除外したいというわけです。そのenumが以下です。

そのクライアントコードです。

実行結果です。

本書ではこのコードには問題があると続きます。このコードは必ず無検査キャスト警告が出ます。警告を無視しても実行は完了し、上記のように庭園の植物をハーブの種類ごとに分類することができますが。この警告はジェネリックスの配列を使用しているためにコンパイル時に型消去が行われ、「Set<Herb>[]」について実行時の型が分からなくなることで発生しています。

次に、配列は、そのインデックスが何を表しているかを知りませんので、手作業で出力にラベル付けをしなければなりませんとあります。配列のインデックスは0,1,2・・・ですが、それらの整数がenumの各定数を表すことにはなりませんね。上記のように配列にアクセスして該当するenumを取り出し、enumからその名前を取得してラベルとして使用するという何段階かの手間が必要になってきます。

その次に、この技法のもっとも重大な問題として「enumの序数でインデックスされている配列へアクセスする際に、正しいint値を使用するのはみなさんの責任」と書かれています。intはenumの型安全性を提供しない、と。このサンプルのようにHerb.Typeの序数でアクセスするか、配列の長さを使ったfor文を使えば誤ったアクセスはしないでしょう。ただ、間違えてしまう可能性が残っているということです。配列には付き物の問題ですね。では、その問題を解決するにはどうすればよいのでしょう。

配列ではなくMap、具体的にはEnumMapを使うのがよい

java.util.EnumMapは、enumをキーとしたMapです。本書ではEnumMapを褒めちぎっています。ということで、まずはそのEnumMapを使ったクライアントコードの修正版です。

実行結果です。

このコードはまず警告が出ません。次にラベル付けですが、EnumMapのtoStringを使うだけでいいのでコードも簡潔、ラベル付けを自分で行わなくてよくなります。そして最後の問題ですが、EnumMapを使ったコードでは序数を使わなくてよくなります。前述のすべての問題が解消され、EnumMapは速度も配列を使っていた場合と比べて遜色ないようです。その理由はEnumMapは内部的には配列を使用しているからとあります。

EnumMapのキーと値は内部的にそれぞれ別の配列に格納されています。それでもキーと値のマッピングが可能なのは、キーとなるenumのordinalをインデックスとしてそれぞれの配列にアクセスしているからです。

EnumMapのコンストラクタは、キー型のClassオブジェクトを受け取ることに注意する

前述のとおりEnumMapはキーとなるenumのordinalをインデックスとして配列にアクセスしています。仮に異なるenumを同じインスタンスのMapのキーにできるならば、異なるenumの同じ序数を配列のインデックスとしてアクセスすることになり、動作が破綻しますね。ということでEnumMapは単一のenumをキーとするMapとなっています。

そのあとに続く「これは境界型トークンであり、実行時ジェネリック型情報を提供している」という部分は、クラスリテラルが実行時の型情報を伝えるためにEnumMapのコンストラクタに渡されていることの説明ですね。これだけでは型トークンですが、EnumMapのクラス宣言を見ると、EnumMapのキーはEnumクラスの再帰型境界となっていて、コンストラクタに渡しているクラスリテラルの型も同じ型パラメータとなっています。

EnumMapのコンストラクタは境界型トークンということが分かります。

EnumMapのもう一つの使用法については次回以降ということで、今回は以上です。

広告
  • LINEで送る