Java の enum
イマドキの Java には enum があるんですよ実は、という話。
知ってるよそんなこと!な人は読むまでもないかも。
enum って?
列挙型のこと。C とか C++ とか C# とかでおなじみのアレ。
単純な enum は Java でもこれらの言語の enum と同じような記述になるけど、これらの言語の enum が整数型をベースにしているのに対して、Java ではオブジェクトをベースにしている点が異なる。
まぁその話は後ほど・・・
単純な enum
ただ列挙するだけの enum なら、本当に C や C++ や C# とほとんど変わらない。
// 信号機の色 enum SignalColor { RED, BLUE, YELLOW }
ただこれだけ。末尾には、余分なカンマがあってもいい。
enum SignalColor { RED, BLUE, YELLOW, }
更に、末尾にセミコロンも記述できる。セミコロンの意味については後ほど・・・
enum SignalColor { RED, BLUE, YELLOW; }
あわせ技。
enum SignalColor { RED, BLUE, YELLOW, ; }
で、これを使うときは、こんな感じ。
static String colorName(SignalColor color) { switch (color) { case RED: return "赤"; case BLUE: return "青"; case YELLOW: return "黄"; default: throw new IllegalArgumentException(); } } public static void main(String[] args) { System.out.println(colorName(SignalColor.BLUE)); }
ここで、case ラベルの指定で型名である SignalColor が不要な点に注意。
switch 文の case ラベル以外の部分、例えば main メソッドでは型名も記述する必要がある。
また、colorName メソッドに null を渡すと、予想に反して NullPointerException が発生する。
なんで IllegalArgumentException じゃなくて NullPointerException になるかは、各自 jad するなり javap するなりして確かめること!
フィールドを持った enum
enum SignalColor {
RED, BLUE, YELLOW;
String colorName;
}
このように、セミコロンの後にフィールドを宣言する。
static String colorName(SignalColor color) { return color.colorName; } public static void main(String[] args) { SignalColor.RED.colorName = "赤"; SignalColor.BLUE.colorName = "青"; SignalColor.YELLOW.colorName = "黄"; System.out.println(colorName(SignalColor.BLUE)); }
また、通常はイミュータブルになるように、
enum SignalColor { RED("赤"), BLUE("青"), YELLOW("黄"); public final String colorName; private SignalColor(String colorName) { this.colorName = colorName; } }
のように記述する。
このように、独自のコンストラクタを記述した場合、その呼び出しは列挙子に小括弧を記述して、その中に引数を指定する*1。
また、コンストラクタのアクセス修飾子には public と protected は指定することが出来ない上、指定しなかった場合でも package private ではなく、private とみなされる。
コンストラクタは複数用意することもでき、列挙子ごとに違うコンストラクタを呼び出すことも可能。
使う側は、
static String colorName(SignalColor color) { return color.colorName; } public static void main(String[] args) { System.out.println(colorName(SignalColor.BLUE)); }
こう。
フィールドが持てるのは、Java の enum が実はタイプセーフ enum というイディオムのシンタックスシュガーとなっているためで、例えば上の例は
class SignalColor { private final String colorName; private SignalColor(String colorName) { this.colorName = colorName; } public static final SignalColor RED = new SignalColor("赤"); public static final SignalColor BLUE = new SignalColor("青"); public static final SignalColor YELLOW = new SignalColor("黄"); }
とほぼ等価*2。
メソッドを持った enum
Java の enum は、フィールドだけにとどまらず、メソッドを記述することもできる。
enum SignalColor { RED, BLUE, YELLOW; String colorName() { switch (this) { case RED: return "赤"; case BLUE: return "青"; case YELLOW: return "黄"; default: throw new IllegalStateException(); } } }
public static void main(String[] args) { System.out.println(SignalColor.BLUE.colorName()); }
一番最初に示した、enum の外側で switch するコードと比べると、colorName メソッド内で NullPointerException が発生しないと言う利点を持つ (Java では this は null にならない)。
また、フィールドとあわせて、
enum SignalColor { RED("赤"), BLUE("青"), YELLOW("黄"); private final String colorName; SignalColor(String colorName) { this.colorName = colorName; } String colorName() { return colorName; } }
とすることで、列挙子を増やした場合に、修正が必要な箇所がなくなる (一箇所追加するだけでいい)。
列挙子ごとにオーバーライド
列挙子ごとにメソッドをオーバーライドすることもできる。
enum SignalColor { RED { @Override String colorName() { return "赤"; } }, BLUE { @Override String colorName() { return "青"; } }, YELLOW { @Override String colorName() { return "黄"; } }; String colorName() { throw new UnsupportedOperationException(); } }
抽象メソッドを持った enum
上の例だと、抽象メソッドにすることでコンパイルチェックすることができる。
enum SignalColor { RED { @Override String colorName() { return "赤"; } }, BLUE { @Override String colorName() { return "青"; } }, YELLOW { @Override String colorName() { return "黄"; } }; abstract String colorName(); }
また、インターフェイスを実装することもできるため、
interface Color { String colorName(); } enum SignalColor implements Color { RED { public String colorName() { return "赤"; } }, BLUE { public String colorName() { return "青"; } }, YELLOW { public String colorName() { return "黄"; } } }
のように、任意のインターフェイスを実装できる。
enum で使用できるメソッドの便利な使い方
values()
Enum クラスに定義されていないので見落としがちだが、各 enum には static メソッドとして values メソッドが定義される。
このメソッドは、全ての列挙子を含む配列を記述された順番で返すので、数値から列挙子オブジェクトへの変換に使用できる。
static SignalColor valueOf(int i) { return SignalColor.values()[i]; } public static void main(String[] args) { SignalColor c = valueOf(1); System.out.println(c.name()); // BLUE }
何か忘れてない・・・?
C とかの enum は基本が数値型なので、フラグとして使用できる。
例えば、
#include <stdio.h> typedef enum hoge { HOGE_1 = 1, HOGE_2 = 2, HOGE_3 = 4, HOGE_4 = 8 } hoge; void f(hoge h) { if (h & HOGE_2) puts("hoge 2"); if (h & HOGE_3) puts("hoge 3"); } int main() { f(HOGE_3); f(HOGE_1 | HOGE_3); return 0; }
こんな感じに 2 のべき乗を値として指定しておくことで、複数のフラグを管理できる。
これを Java の enum で実現しようとする場合、EnumSet を使用する。
enum Hoge { HOGE_1, HOGE_2, HOGE_3, HOGE_4 } static void f(EnumSet<Hoge> h) { if (h.contains(Hoge.HOGE_2)) System.out.println("hoge 2"); if (h.contains(Hoge.HOGE_3)) System.out.println("hoge 3"); } public static void main(String[] args) { f(EnumSet.of(HOGE_3)); f(EnumSet.of(HOGE_1, HOGE_3)); }