Javaではint定数の代わりにenumを使用する

最近はEffective Javaをずっと読んでいる。Javaでは定数宣言にpublic static final intを使わず、enumを使えとのことだったのでメモしておく。

従来の定数宣言

従来は、Javaで定数宣言をするときは、以下のように宣言していた。

//色の黒と白
public static final int COLOR_BLACK = 0;
public static final int COLOR_WHITE = 1;

//アニメ「プリキュア」のキュアブラックとキュアホワイト
public static final int PRECURE_BLACK = 0;
public static final int PRECURE_WHITE = 1;

この方法は、int enumパターンとして知られており、以下のような多くの欠点を持っている。

  • 名前空間を持たないので、変数名に接頭辞(COLOR・PRECURE)を多用することになり、冗長である。
  • コンパイル時に呼び出した場所へインライン展開されてしまう(例:Integer apiVersion = Api.VERSION;がコンパイルされると、Integer apiVersion = Integer.valueOf(10);になる)ため、再コンパイルの必要がある。
  • 定数をデバッガから表示しても見えるのは数字であり、あまり役に立つ情報ではない。
  • グループ内の定数を全てイテレートしたり、グループの大きさを得たりする信頼できる方法が提供されていない。

int enumパターンの代わりに、これから説明するenum型を使用することでこれらの欠点を全て克服することができる。

enum型とは

enum型は、あるカテゴリーに属した複数の定数をひとまとまりとして取り扱うための仕組みである。J2SE 5.0のenumは、CやC++のenumのように内部的に整数で定数を保持するものではなく、タイプセーフenumと呼ばれるパターンを実現したものになっている。

一番単純なenum

一番単純なenumの定義方法は以下のようになる。

public enum Color { RED, BLUE, YELLOW, BLACK, WHITE }

これは定数名そのものが定数を表しており、上記例ではColor.REDのように呼び出すとREDが出力される。

enumは内部的にはクラスであり、定義する場所はクラスを定義する場所と同じで、enum型もクラス同様パッケージに属する。

基本的には1ファイルに1つのenum型を定義するが、内部クラスのように他のクラスの内部にも定義することができる。アクセス権やアクセス方法もクラスの場合と同じである。

次のように記述することで、上記のColorクラスを利用することができる。

import define.common.Color;
public class EnumTest {
    public static void main(String[] args) {
        Color color = Color.WHITE;
        System.out.println("Color is " + color); //"Color is WHITE"を出力する。
    }
}

enumの仕組みと便利メソッド

enumをjavapコマンドで逆コンパイルすると、以下のように表示される。

Compiled from "Color.java"
public class Color extends java.lang.Enum{
    public static final Color RED;
    public static final Color BLUE;
    public static final Color YELLOW;
    public static final Color BLACK;
    public static final Color WHITE;
    static {};
    public static final Color[] values();
    public static final Color valueOf(java.lang.String);
}

Colorクラスはjava.lang.Enumクラスを継承しており、内部的にはpublic static finalで定数を作っていることが分かる。

ここで定数以外にもvaluesとvaluesOfというメソッドが宣言されていることに注目してほしい。valuesメソッドは宣言された定数全てを含む配列を返すメソッドである。valueOfメソッドは文字列から定数インスタンスを取得するメソッドである。

enumに値を与える

普通の定数のように定数名 = 値のようにenumを使用することもできる。

public class EnumTest {
	public enum Precure {
		BLACK("なぎさ"),
		WHITE("ほのか");

		private String jp;

		Precure(String jp) {
			this.jp = jp;
		}

		public String getJp() {
			return this.jp;
		}
	}

	public static void main(String[] args) {
		System.out.println(Precure.BLACK.getJp());
	}
}

Precure.BLACK.getJp()が呼び出されると、「なぎさ」という文字列が返される。

その仕組みとしては、enum内の各定数(BLACK, WHITE)はインスタンスなので、実行時には暗黙的にコンストラクタが呼び出される。つまり、BLACK("なぎさ")で「なぎさ」という文字列をコンストラクタに渡している。

定数ごとに異なる振る舞いを持たせる

以下のように記述することで、定数ごとに異なる振る舞いを設定することが出来る。

public class EnumTest {
	public enum Precure {
	    BLACK() {
	        public String apply(String s) {
	            return "<span style=\"color:#000\">" + s + "</span>";
	        }
	    },
	    WHITE() {
	        public String apply(String s) {
	            return "<span style=\"color:#FFF\">" + s + "</span>";
	        }
	    };

	    //抽象メソッドで、それぞれの定数にapplyメソッドの実装を強制する
	    abstract public String apply(String s);
	}

	public static void main(String[] args) {
		System.out.println(Precure.BLACK.apply("キュアブラック!"));
	}
}

Precure.BLACK.apply("キュアブラック!")が呼び出されると、<span style="color:#000">キュアブラック!</span>が返される。

参考
書籍 Effective Java
4. enum | TECHSCORE(テックスコア)
J2SE 5.0 Tiger 虎の穴 Typesafe Enum