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

日々常々

ふつうのプログラマがあたりまえにしたいこと。

enumあれこれ

EffectiveJava読書会で意外とenumネタで需要がある気がしたので、ちょっと書いてみます。内容的に目新しいものは無いはずの事を書きますので、理解されている方はツッコミ所探しでもしてください。

読み方

enumは enumerate か enumeration が語源になってるわけで、素直にいけば「イニュム」になります。けれど実際どう読まれるかは別の話です。最も大事なのは伝わる事なので、私は基本相手が発音したものに合わせます。コウモリです。それはいい。
実現場では「いーなむ」と発音することが多いです。アクセントの位置は地域で変わるかもしれませんが、日本では「いーなむ」と言っておけば通じない事はないと思います。「いにゅーむ」とか下手に言っちゃうと「は?」って顔される可能性が非常に高いです。英語のセッションの動画見て「い(え)にゅーむ」って言う事にしたのですが、いざ言おうとしたら「いにゅー……(相手の顔をみて)…いーなむ」となったので、最近は諦めて「いーなむ」って言ってます。
この時期のツイートがこんな感じ。

読書会でもこの話題になって「列挙型って言っておけば問題ない」と言う結論に至ってたりします。

書き方

シンプルなenumは本当にシンプルです。enumに続けて型名を書く。その後に列挙する。それだけです。

enum EnumHonmono {
	FOO,
	BAR
}

で、これをjavapするとこうなってます。単純に列挙した名前のフィールドを持つクラスです。

final class EnumHonmono extends java.lang.Enum{
    public static final EnumHonmono FOO;
    public static final EnumHonmono BAR;
    private static final EnumHonmono[] ENUM$VALUES;
    static {};
    private EnumHonmono(java.lang.String, int);
    public static EnumHonmono[] values();
    public static EnumHonmono valueOf(java.lang.String);
}

privateのコンストラクタ。外部からインスタンスを作る事はありません。
valuesは列挙された全要素が返されるので、for-eachに使えたりします。
列挙名に対応するインスタンスを返すvalueOfが定義されています。
java.lang.Enum 由来のメソッドもありますが、後述の compareTo と toString が列挙名をそのまま文字列で返す事*1くらいを知っておけば良いんじゃないでしょうか。

比較

列挙したものを比べたくなるのは自然です。比べると言えば java.lang.Comparable#compareTo(T) です。enum の親クラスになる java.lang.Enum はこのインタフェースを実装していて、compareTo も実装済みです*2。この compareTo は ordinal を使用します。ordinal は列挙された順に1ずつ増えていくint値です。なので列挙された順番で大小が決まります。

ここで使用される ordinal は ordinal() メソッドで取得できたりしますが、これを使用する事は推奨されません。この点については【項目31 序数の代わりにインスタンスフィールドを使用する】を参考にしてください。多少冗長な気もしますが、どうしても個別の数値が必要ならば例で示されているようにコンストラクタ経由で値を持つようにするのがいいです。この値をDBに格納したり、クライアントに返したばかりに順番を変えられない(新しい項目は後ろに追加するしかなくなる)とかになるのは悲しい事です。

メソッドを書く

enumメソッドを書くのは非常に簡単です。普通のクラスと同じく書けば普通にメソッドになります。各列挙によって挙動を変える方法は2通りあります。インスタンスフィールドを参照するか分岐するかです。以下にそれぞれの例を書くとこんな感じになります。

enum Field {
	A("aaa"),
	B("bbb")
	;
	private final String value;
	Field(String value) { this.value = value;}

	String value() { return value; }
}

enum Switch {
	A,
	B
	;

	String value() {
		switch (this) {
		case A: return "aaa";
		case B: return "bbb";
		default:
			throw new AssertionError();
		}
	}
}

【項目30 int 定数の代わりに enum を使用する】のP.147-では列挙によって切り替えるのは問題が多いとされており、次に書く抽象メソッドの使用が推奨されています。

抽象メソッドを書く

enumを無理矢理既存のJavaの考え方で説明すると、列挙された数だけインスタンスを持つ抽象クラスと考えると判りやすいかもしれません。抽象クラスなのでメソッドが持てる。抽象メソッドも持てる。抽象メソッドはどこかで実装しなくてはいけない。この場合、実装箇所は各列挙時です。「シンプルに列挙出来る!」と喜んでたのに、メソッド実装すると無茶苦茶見難くなる。これは結構悩ましいところだったりします。いい解決策はないものでしょうか。

enum EnumHonmono {
	FOO {
		void abstractMethod() {}
	},
	BAR {
		void abstractMethod() {}
	}
	;
	abstract void abstractMethod();
	void method() {}
}

javapするとこうなります。「抽象クラスみたいなもの」とか言いましたけど、本当に抽象クラスでした。そりゃそうですね…。

abstract class EnumHonmono extends java.lang.Enum{
    public static final EnumHonmono FOO;
    public static final EnumHonmono BAR;
    static {};
    abstract void abstractMethod();
    void method();
    public static EnumHonmono[] values();
    public static EnumHonmono valueOf(java.lang.String);
    EnumHonmono(java.lang.String, int, EnumHonmono);
}

EffectiveJavaでは抽象メソッドや戦略enumパターンが推奨とされていますが、これに関しては状況次第なところがあると思います。メリット/デメリットやコードとして悶々と考えると従うほうがいい気もするのですが、enum自体をよく判っていない人が多い中で使うのは非常に危険です。「enumで遊んでみた!」みたいな人が不在な状態でenumを使用する場合、列挙と単純な値を返すメソッド程度にしてしまったほうが安全です。enumはそれだけでも十分強力ですし。

よくある「定数クラス」との互換

定数クラスってありますよね。定数値のみを定義した変なクラス。

public class IntConst {
	public static final int CONST_1 = 1;
	public static final int CONST_2 = 2;
	public static final int CONST_3 = 3;
}

こういうのは型安全ではありませんし、enumで列挙してしまうのが良いです。ですが、定義した数値を使いたい事*3コンストラクタ経由で値を持つ事で代替できます。こんな感じで。

public enum EnumConst {
	CONST_1(1),
	CONST_2(2),
	CONST_3(3)
	;

	private final int value;
	EnumConst(int value) {
		this.value = value;
	}

	public int intValue() {
		return value;
	}
}

多少冗長になってしまいますが、intでなくenumを用いる事で予期せぬ数値が入ったりマジックナンバーでのハードコーディングを避けたり出来るので、ヒューマンエラーによる見つけ難いバグを回避できる可能性があります。この実装をしたとしても、たいていの場合はintでなくenumでの代替ロジックがあるはずなので、数値を持たせたとしても依存しないほうが良いと思います。

もろもろ

enumJava5で追加されたものです。とはいえJava5が出たのは2004年9月30日。そして既にサポートも終わっているので、とてもじゃないけど新しいものとはいえません。なのにenumは「使わなければ何か出来ない」と言う訳でもなく、使用しない現場もあるようです。便利なのに。使える場所には積極的に使って良いと思うんです。
enumインスタンスを制御できないなど、うっかり使うとテストし辛いところもあります。この点に関しては【項目34 拡張可能な enum をインタフェースで模倣する】のような感じでenumからインタフェース抽出し、インタフェースで扱うのが良いと思います。オブジェクト接合部*4として切り離してしまう感じですかね。
enumはクラスなので何でも出来ます。そして何でも出来すぎます。便利ですけど、やりすぎてしまう事もあるかなと思います。個人的には「やりすぎればいいんじゃないかなー」と思ってます。他のいろんなものと同様に、一度やりすぎて適切っぽいラインを見極めるとか、そんな感じの体当たりが好きです。

*1:つまりtoStringで取得した値をそのままvalueOfに突っ込めばenumインスタンスが取得できます。

*2:実装済みなのは良いんですけど、finalなのでたまに迷惑だったりします。

*3:値をDBに格納したりとか、その数値に依存したロジックを組んだりするとか。

*4:「レガシーコード改善ガイド」参照