列挙型


Enumクラス

従来の Java 言語、及び Java Framework API の大きな問題の一つに、列挙型が存在しないということがありました。 そのため、Java API では、方向や種類を表すための一定の定数コレクションをインタフェースの数値定数メンバとして管理していました。 確かにこの方法で実装することは可能でしたが、便利なものとはいえません。

例えば、方向を表す定数は javax.swing.SwingConstants インタフェースで定義されています。 メンバは全て数値で、SwingConstants.BOTTOM や SwingConstants.LEFT という形で値を得ることができました。

SwingConstants インタフェースを実装するクラスの例に javax.swing.JLabel などがあります。 ラベル内容の X 軸に沿った配置方法を指定する getHorizontalAlignment() メソッドは方向を表す定数を引数から受け取りますが、引数の型は SwingConstants ではなく、int 型です。 すなわち、SwingConstants で定義されていない未知の値を誤って指定してしまう可能性が常にあるのです。 受け取れる範囲がコンパイル時点で確定しているにもかかわらず、受け取った値が適切なものかどうかを判断するには実行しなければわかりません。

label.getHorizontalAlignment(SwingConstants.LEFT); //OK
label.getHorizontalAlignment(1234); //誤りだがコンパイルに成功する

新しい Java 言語、及び Java API では、このような問題を解決するために java.lang.Enum 型が追加されています。 Enum 型は安全な列挙型を実現するためのクラスで、このクラス型は言語仕様にも組み込まれています。

java.lang.Object
 |
 +-- java.lang.Enum<E>

public abstract class Enum<E extends Enum<E>>
	extends Objectimplements Comparable<E>, Serializable

C 言語とは異なり、Enum 型も Java にとっては他のクラスと同様に Object 型を基本とするクラスにすぎません。 しかし、Enum 型は言語仕様に組み込まれているため、Enum クラスを継承して同行するような面倒なコードを記述する必要はありません。 C 言語のように、enum キーワードに続いて定数を列挙するだけで、新しい列挙型を宣言することができるのです。

修飾子 enum 型名 { 識別子1, 識別子2, ... }

enum 型は他のクラス型のように public 修飾子を指定することができます。 enum 型を記述できるスコープはクラスと同様であると考えることができ、パッケージのメンバ、または内部クラスとなることができます。 定数となる enum 型のメンバは変数などと同様に Java の識別子に従えばどのような名前でも可能ですが、一般的な命名規則として全て大文字のアルファベットを使います。

enum 型のメンバは、数値などの定数ではなく、全てが static な Enum 型を実装するオブジェクトです。 Enum オブジェクトは内部に name() メソッドを保有し、自分自身を識別する文字列を返します。 同様に、オーバーライドされない限り toString() メソッドが返す値も name() メソッドと同じです。

enum OStan { 
	WIN95, WIN98, WINME, WIN2K, WINXP
};

class Test {
	public static void main(String args[]) {
		System.out.println(OStan.WIN95);
		System.out.println(OStan.WIN98);
		System.out.println(OStan.WINME);
		System.out.println(OStan.WIN2K);
		System.out.println(OStan.WINXP);
	}
}
>java Test
WIN95
WIN98
WINME
WIN2K
WINXP

このプログラムは、OStan 列挙型を新たに定義し、OStan 型のメンバを標準出力に表示するという単純なプログラムです。 出力された結果から、OStan 列挙型に保有されている static な OStan オブジェクトは、テキスト上と同じ名前を持っていることがわかります。

System.out.println() メソッドは、オブジェクトの toString() メソッドの結果を表示していますが、例えば OStan.WINME.name() と記述しても同じ結果が得られます。 たたし、name() メソッドは final 修飾子で宣言されているため確実に列挙型の識別名を得ることができますが、toString() メソッドはオーバーライド可能であるという点で異なっています。

このプログラムで生成した OStan 型も、実質的には OStan クラス型にすぎません。 コンパイルの結果 OStan.class ファイルが生成されるので、これを javap コマンドで調べれば、その内容を確認できるでしょう。

>javap OStan
Compiled from "Test.java"
final class OStan extends java.lang.Enum{
    public static final OStan WIN95;
    public static final OStan WIN98;
    public static final OStan WINME;
    public static final OStan WIN2K;
    public static final OStan WINXP;
    public static final OStan[] values();
    public static OStan valueOf(java.lang.String);
    static {};
}

この結果から、OStan クラスは列挙型のスーパークラスである Enum を継承し、final 宣言されていることがわかります。 また、OStan 列挙型の定数メンバは、すべて OStan 型の static なオブジェクトであることもわかります。 int 型を定数にする場合とは異なり、これならば確実な定数範囲を得ることができます。

enum ServerntClass { 
	SAVER , ARCHER , BERSERKER , LANCER ,
	ASSASSIN , CASTER , RIDER
 }
class Servernt {
	private ServerntClass servernt;
	private String name;

	public Servernt(String name , ServerntClass servernt) {
		this.name = name;
		this.servernt = servernt;
	}
	public String toString() { return name + " : " + servernt; }
}
class Test {
	public static void main(String args[]) {
		Servernt saver = new Servernt("アーサー王" , ServerntClass.SAVER);
		Servernt archer = new Servernt("エミヤ" , ServerntClass.ARCHER);
		Servernt assassin = new Servernt("佐々木小次郎" , ServerntClass.ASSASSIN);

		System.out.println(saver);
		System.out.println(archer);
		System.out.println(assassin);
	}
}
>java Test
アーサー王 : SAVER
エミヤ : ARCHER
佐々木小次郎 : ASSASSIN

このプログラムの Servernt クラスの役割はオブジェクトの名前と、列挙型 ServerntClass を関連付けるものとします。 これまでの Java であれば、ServerntClass をインタフェースで宣言し、定数メンバは int 型などの final static 変数で実現していました。 しかし、この場合はまったく関係のない値が指定される可能性もあるため、SAVER , ARCHER , BERSERKER , LANCER , ASSASSIN , CASTER , RIDER のいずれかだけを指定するという範囲情報の管理には問題がありました。 ところが、上記のコードを見てわかるように、新しい Java での Servernt クラスは ServerntClass 型という独立した列挙型として定数を受けることができるため、誤った値を渡される可能性をなくすことができるのです。

javap で enum クラスを調べると、enum 型には定数メンバ以外に、全ての定数メンバを返す static な values() メソッドと、文字列から適切な定数オブジェクトを返す valueOf() メソッドがあることもわかります。 列挙型のメンバをまとめて for-each にかけたいときなどには、こうしたメソッドを使うと便利でしょう。

enum OStan { 
	WIN95, WIN98, WINME, WIN2K, WINXP
};

class Test {
	public static void main(String args[]) {
		for(OStan obj : OStan.values()) {
			System.out.println(obj.name());
		}
	}
}

このプログラムの実行結果は、OStan 列挙型のメンバをすべて標準出力に表示するだけです。 最初のプログラムとの違いは、手動でメンバを記述するのではなく、foe-each 文でまとめて処理してしまっている点です。


enum 型の比較

Java 言語の switch 文に指定できる式は、int、short、byte、char のいずれかでなければなりませんでした。 しかし、新しい Java の言語仕様では、これに加えて enum 型のオブジェクトを switch 文に指定できるようになりました。 enum 型のオブジェクトは参照型ですが、オブジェクトの型から case に指定することができる値はおのずと限られます。

enum 型のオブジェクトを switch 文に指定した場合、case に指定する定数の型は enum 列挙型のメンバに限定されます。 そのため、case には直接定数の名前を指定することができ、それ以外の値を指定することはできません。

switch(ostan) {
	case WIN95: ... //OK
	case null: ... //エラー。null は指定できない
	case OStan.WINME: ... //エラー。OStan. は不要
	case "WINXP": ... //エラー。OStan の定数メンバ以外は指定できない
}

列挙型はオブジェクトですが、null を定数として指定することはできません。 switch 実行時に式が null の場合は、NullPointerException が発行され例外となります。

enum OStan { 
	WIN95, WIN98, WINME, WIN2K, WINXP
}
class Test {
	public static String getOStan(OStan ostan) {
		switch(ostan) {
		case WIN95:
			return "一途な、女ですから";
		case WIN98:
			return "ボクだって、やる時はやる女だよ";
		case WINME:
			return "壊れる、女ですから";
		case WIN2K:
			return "頼れる、女ですから";
		case WINXP:
			return "おせっかいな、女ですから";
		}
		return null;
	}

	public static void main(String args[]) {
		for(OStan obj : OStan.values()) {
			System.out.println(obj + "=" + getOStan(obj));
		}
	}
}
>java Test
WIN95=一途な、女ですから
WIN98=ボクだって、やる時はやる女だよ
WINME=壊れる、女ですから
WIN2K=頼れる、女ですから
WINXP=おせっかいな、女ですから

このプログラムは、getOStan() メソッドに OStan オブジェクトを与えると、switch 文で渡された定数を判断して対応した文字列を返すというものです。 このような判断は if-else 文の入れ子でも記述できますが、その場合は if 文のたびに if(ostan == OStan.WIN95) { ... } というように記述しなければなりません。 switch 文であれば、非常にスマートに定数ごとの処理を書くことができます。


enum クラスの拡張

enum 型は本来、定数のコレクションとしての役割に集中するべきですが、要求によってはこれに関連した振る舞いをパックしたいと考えることがあるでしょう。 少なくとも enum の実体は Enum クラスを拡張したクラスなのだから、定数以外にフィールドやメソッドを持ってもおかしくはないはずだと考えることができます。

実際に enum 型の宣言では、定数メンバ以外に、必要に応じてフィールド、メソッド、コンストラクタを記述することができます。 enum 宣言でメンバを追加するには、定数の列挙の末尾にセミコロン ; を指定し、その後に記述します。 定数の末尾のセミコロンの後は、通常のクラスのようにメンバを記述できますが、enum で定義される values() や valueOf() メソッドと衝突するようなメンバは書けません。 また、列挙型のオブジェクトはインスタンスかできないため、コンストラクタは private 以外を指定することはできません。

enum OStan { 
	WIN95, WIN98, WINME, WIN2K, WINXP;

	public String getText() {
		switch(this) {
		case WIN95:
			return "一途な、女ですから";
		case WIN98:
			return "ボクだって、やる時はやる女だよ";
		case WINME:
			return "壊れる、女ですから";
		case WIN2K:
			return "頼れる、女ですから";
		case WINXP:
			return "おせっかいな、女ですから";
		}
		return null;
	}
}
class Test {
	public static void main(String args[]) {
		for(OStan obj : OStan.values()) {
			System.out.println(obj + "=" + obj.getText());
		}
	}
}

このプログラムは、先ほどの switch 文のプログラムの getOStan() メソッドと同じ振る舞いをする getText() メソッドを OStan 列挙型に組み込んだものです。 enum 型のオブジェクトは、このような独自のメソッドを提供することができるため、単純な列挙型というだけではなく定数ごとの振る舞いを実装することができます。

enum 型のクラスはコンストラクタを独自に定義することができますが、enum 型は継承したり new からインスタンス化することはできないため、常に private なコンストラクタとなります。 そのため、コンストラクタには private 以外のアクセス修飾子を指定することはできません。 アクセス修飾子を省略した場合も private となります。

コンストラクタに引数を渡すには、enum の定数の定義で、定数名の後に ( ) を指定することでコンストラクタ起動式となります。

enum OStan { 
	WIN95("一途な、女ですから"),
	WIN98("ボクだって、やる時はやる女だよ"),
	WINME("壊れる、女ですから"),
	WIN2K("頼れる、女ですから"),
	WINXP("おせっかいな、女ですから");

	private final String text;
	private OStan(String text) { this.text = text; }
	public String toString() { return name() + "=" + this.text; }
}

class Test {
	public static void main(String args[]) {
		for(OStan obj : OStan.values()) {
			System.out.println(obj);
		}
	}
}

このプログラムでは、定数宣言の定数識別子の直後に ( ) によってコンストラクタに渡す引数を指定してます。 これは、次の文と同一であると考えることができます。

public final static OStan WIN95 = new OStan("おせっかいな、女ですから")

このコンストラクタによる初期化を用いれば、定数オブジェクト固有の独自の振る舞いを簡単に実現することができます。



前のページへ戻る次のページへ