enum basics

仕事柄、Javaenumを使うことが多いのですが、あまり世の中のプログラムでenumを全力で使ってるところをあまり見かけません。

ということで、自分でもどうなのと思うときがちらほらあるenumの使い方をまとめてみようと思いますが、とりあえず今回は普通の復習用に基本的な機能を。

型付の定数が宣言できる

enumはもともとCのint形の値に名前をつけただけじゃなくて、型安全な列挙定数が作れるってことでJava5から導入されました。

こんな感じ。

public enum Sample {
    HOGE,
    FOO,
    BAR,
}

で、世の中で見かけるenumは上記くらいのシンプルなのが多いみたいです。

クラス本体が宣言できる

定数定義のあとに ";" で区切ると、以降をクラスの本体としてメソッドとか定義できます。

public enum Sample {
    HOGE,
    FOO,
    BAR,
    ; // セミコロンで区切る
    public boolean isHoge() {
        return this == HOGE;
    }
}

switch-caseがかける

switch-caseを使う際に、まずswitchのセレクタ式の部分にenum型の値をそのまま突っ込めます。で、caseラベルの値には定数の名前をそのまま書けばOK。

Sample s = ...;
switch(s) {
  case HOGE:
    ...
  case FOO:
    ...
  case BAR:
    ...
}

Comparable, Serializable

この辺が勝手に実装されてます。とくにSerializableは人間が書くと忘れがちな部分とかもちゃんとやってくれているので、enum化できるときはそうしたほうがいいかと。

引数つきの定数が宣言できる

定数宣言のあとに()で引数指定できます。1個以上の引数を使う場合は、プライベートコンストラクタの宣言も必要。

public enum Operator {
    ADD("+"),
    SUB("-"),
    ;
    private final String symbol;
    private Operator(String symbol) {
        this.symbol = symbol;
    }
    public String getSymbol() {
        return symbol;
    }
}

ちなみに、enumはだいたい次のように書き換えられます。

public class Operator {
    public static final Operator ADD = new Operator("+");
    public static final Operator SUB = new Operator("-");
    private final String symbol;
    private Operator(String symbol) {
        this.symbol = symbol;
    }
    public String getSymbol() {
        return symbol;
    }
}

ただし、このほかにもいろいろと自動でやってくれるので、上のコード書くくらいなら素直にenum使いましょう。

定数ごとに匿名クラスブロックをかける

定数は引数の指定だけじゃなくて、newのときみたいに匿名クラスブロックをかけます。
enum自体のクラス本体にabstractメソッドを書いてオーバーライドすることもできます。

public enum Operator {
    ADD {
        @Override public String getSymbol() {
            return "+";
        }
    },
    SUB {
        @Override public String getSymbol() {
            return "-";
        }
    },
    ;
    public abstract String getSymbol();
}

こいつも、だいたいこんなことが行われてます。

public abstract class Operator {
    public static final Operator ADD = new Operator() {
        @Override public String getSymbol() {
            return "+";
        }
    };
    public static final Operator SUB = new Operator() {
        @Override public String getSymbol() {
            return "-";
        }
    };
    public abstract String getSymbol();
}

クラスファイル覗いてみると判るのですが、本当にだいたいこんな感じになっているため、ADD.getClass()とかやるとOperator型じゃなくてそのサブクラスが返ってきます。
この代わりに、ADD.getDeclaringClass()って言うのが用意されているので、そちらを使ったほうが安全だと思います。

親インターフェースを指定できる

普通のクラスと同様に、implementsで親インターフェースをかけます。

この辺からあまりなじみがないかもしれませんが、Effective Javaなんかでもこのあたりのパターンがいくつか紹介されていました。

public enum Sample implements Runnable {
    HOGE {
        public void run() {
            System.out.println("hogehoge");
        }
    },
    FOO {
        public void run() {
            System.out.println("fofo");
        }
    },
}

言語仕様から見た基本的なところはこんなもんだと思います。復習はこのくらいで。