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

項目30 int定数の代わりにenumを使用する

enumとアノテーションの章、ジェネリックスに続いてJ2SE 5.0から導入された機能についての話です。この項目はenumについての基本的な説明が主で、10ページと長いです。enumは強力で便利な機能なので、ぜひマスターしたいところです。

列挙型とは

本書には列挙型(enumerated type)というのは、季節や太陽系の惑星、トランプのスーツ(スペードやハート)などの固定数の定数からその値が成り立つ型、とあります。つまり、ある有限個の名前(識別子)を並べた(列挙された)ものがあり、その列挙された識別子をまとめたもの(列挙)を型として扱うのが列挙型です。

J2SE 5.0より前のJavaにはこの列挙型を表現する仕組みがありませんでした。そのときによく使われたのが以下のようなint定数を用いたパターンです。

このパターンをint enum パターンと呼んでいるそうです。列挙型自体はPascalやC言語、C++などJava以前からある言語にも導入されてましたので(C言語には後になって導入されたようですが)、enumという言葉を使っているのだと思います。

で、このint enumパターンは、その型の個々のメンバーに対して、名前付きint定数のグループを宣言するというものです。なぜintなのかというと、C言語の列挙型が列挙された識別子に対して整数値を割り当ててていたからだと思います。その流れかと。

そしてこのint enum パターンはint定数を並べただけですので、そのプログラムは脆弱だということです。その理由が続きます。

int enumはコンパイル定数

Javaの定数(static finalフィールド)はコンパイル時にインライン展開されます。本書では「定数を使用しているクライアントの中にコンパイルされてしまいます」と書かれているところです。

インライン展開とは、定数を参照するためにコードに定数名を記述している箇所において、コンパイル時にその定数名が定数値に置換されるということです。何のためにこういうことを行っているかというと、switch文のためです。switch文で同じ値の定数をcase文の判定値にした場合、switch文は同じ値のcase文を許容しないため、コンパイルエラーになります。このコンパイルエラーの抜け道にさせないように定数はインライン展開されます。

定数がコンパイル時に値に置換されるということは、定義されている定数の値をコード上で修正しても、再コンパイルしなければその値は変わらないということになります。その場合の動作は保証されないというのが、本書の次の話ですね。

int定数を、表示可能な文字列へ変換する簡単な方法はない

本書では「定数をプリントしたり、デバッガから表示したりすると、見えるのは数字であり、あまり役に立ちません」とあります。int定数はint型なのでSystem.out.printやprintfなどを使うと数値しか表示されません。デバッガから表示というのはjdbなどのコマンドラインのデバッガでしょうか、確かに数値だけになると、どの定数で使われた値なのか、そもそも定数なのかどうかも分かりませんね。

次のint enum定数をイテレートしたり、int enumグループの大きさを得たりするための信頼できる方法も・・・ないですね。サブクラスで同じグループの定数を増やしたりするとコードはますます混沌としてきます・・・。

String enumパターンとして知られるその変形は、さらに望ましくない

int enumパターンのint定数をStringの文字列定数に変えるパターンもありますが、その場合はもっと望ましくない結果になるようです。文字列定数、例えば「static final String APPLE_FUJI = “Apple.FUJI”;」などとすれば表示可能な文字列になります。ただ、この場合は文字列比較に依存するようになり、パフォーマンスに悪い影響を与えます。

もっと悪いことに、例えば初心者のユーザがクライアントに定数を使わず、定数の値をハードコードしてしまうことも考えられます。さらにそのときハードコードした文字列にスペルミスがあったとしても、コンパイルが通ってしまい、実行時のバグになります。

Javaのenum型は、本格的なクラス

上記のint enumパターンやString enumパターンの欠点を補い、多くの恩恵をもたらすのがJavaの列挙型であるenum型です。その書式は以下のとおり。

本書ではC言語やC++、C#などと違い、Javaのenum型は本格的なクラスであるとあります(Javaの言語仕様を見ると、enum型は特別なクラスだとあります)。そしてenum型がどういったクラスかという話が続きます。ただ、筆者は単純と言っていますが、次の段落の内容は概要を知らない人にとっては想像し難しいです。

Effective Java 第1版において、筆者はタイプセーフenumパターンというものを紹介していました。まだenum型がないバージョンのJavaで列挙型を実現するための方法です。実は現在のJavaのenum型はそのタイプセーフenumパターンを言語でサポートしたものなのだそうです。ですので、そのタイプセーフenumパターンの内容を1段落でざっと説明しています。第1版を知らない人は置いてけぼりですね・・・私は第1版持ってましたが、タイプセーフenumパターンの項目は理解できずに読み飛ばしてました。しかも第1版、おそらく捨てました・・・そしてGoogle検索でも第1版の解説が出てこない・・・何やってるんだか。

ないものはしょうがないので、本書の内容から書き起こしてみます。すると、タイプセーフenumはこういったクラスだとなります。

enum型は、それぞれの列挙定数に対して、1つのインスタンスを公開しています。そして、enum型のクラスは事実上finalになっているため、new演算子によるインスタンスの生成はできないし、クラスの継承もできません。そしてこの形、項目3に出てきたシングルトンの例とほぼ同じです。こうしてコードにしてみると本書の内容はすぐ分かりますね。実際には暗黙的にjava.lang.Enumを継承していたりさまざまなことをやってますが、クラス本体のイメージは上記のとおりです。

つまりJavaの列挙型は、C言語などのような整数ではなく、インスタンスの列挙ということになりますね。

今回はint定数の代わりにenum型が登場したところまでです。次回はenum型の特徴についてということになります。

広告
  • LINEで送る