SEチャンネル

ITについてできる限り書くメモ

あなたの知らないJava enumの使い方

f:id:tkmtys:20151120002549j:plain

前回に引き続き、今回もJava文法の少し踏み込んだ内容を解説する。今回はenum(列挙型)である。enumといえば定数のリストを定義するだけでしか使っていない人も多いのではないだろうか。実はJavaenumは処理を記述したり、コンストラクタを用意したりと、一般のclassとほぼ変わらない使い方ができる。

以前書いたstatic修飾子の解説はこちらからどうぞ。 tkmtys.hatenablog.com

準備

今回のサンプルコードの元として、以下のCountryを使用する。単純に各国要素が定義されているだけである。

public enum Country {
    America,
    China,
    Japan,
    Germany,
    TheUnitedKingdom,
}

各要素にアクセスする場合は以下のようにする。

Country c = Country.America;

sponsor

enumで定数を定義する

enum内でpublic static finalを指定した変数を定義すれば定数として利用できる。非常によく使われるテクニックだ。

public enum Country {

    America,
    China,
    Japan,
    Germany,
    TheUnitedKingdom,

    ; // セミコロンで要素とクラス定義を区切る

    public static final Country[] EuropeanCountries = {
        Germany,    
        TheUnitedKingdom,   
        ... // その他のヨーロッパの国をリストする
    };
    public static final Country[] AsianCountries = {
        China,
        Japan,  
        ... // その他のアジアの国をリストする
    };
}

実際の使われ方としては、意味のある部分集合を定義してfor-each文で使用する場合が多い。デフォルトで用意されているvalues()で全要素を取得できることと合わせて覚えておくとよい。

// アジアの国をfor each
for (Country asia : Country.AsianCountries) {
    // 処理
}
// 全要素をfor each
for (Country c : Country.values()) {
    // 処理
}

enumで変数/コンストラクタ/メソッドを定義する

先ほどはpublic static finalで定数を定義したが、普通のclassと同様にenumでも変数、コンストラクタ、メソッドを定義することができる。次の例はカントリーコードと日本語名の変数を定義し、コンストラクタで変数をセットしている例だ。変数やメソッドの記述の仕方は通常のclassの場合と全く同じである。

public enum Country {

    /**
    * ()でコンストラクタに引数を渡すことができる
    */
    America(1, "アメリカ"),
    China(86, "中国"),
    Japan(81, "日本"),
    Germany(49, "ドイツ"),
    TheUnitedKingdom(44, "イギリス"),

    ;
    
    private int code; // カントリーコード
    private String japaneseName; // 日本語名

    /**
    * コンストラクタ
    * かならずprivate指定でないといけない
    */
    private Country (int code, String japaneseName) {
        this.code = code;
        this.japaneseName = japaneseName;
    }
    public int getCode(){
        return code;
    }
    public String getJapaneseName(){
        return japaneseName;
    }
}

1つ注意しないといけないのが、enumのコンストラクタは必ずprivate指定でないと行けない点で、private以外を指定した場合はコンパイルエラーとなることだ。これはenumの要素が新規生成されるとまずいからである。

各要素からメソッドアクセスする場合は以下のようになる。

Country c = Country.China;
int code = c.getCode();
String japaneseName = c.getJapaneseName();

enumでabstractメソッドを定義する

先ほどは各要素ごとに変数を定義した。では一歩進んで要素ごとにロジックを変更したい場合、どうすればよいのだろうか?なんとJavaではenum内でabstract指定したメソッドが定義できる。これはつまりenum自体は単なるabstract classであり、各要素がそれぞれ異なる実装だということである。Countryに国ごとに実装の異なるメソッドcalcSomethingを定義してみよう。

public enum Country {

    /**
    * {}の中にabstractメソッドの実態を定義できる
    */
    America(1, "アメリカ") {
        @Override
        public double calcSomething(double x) {
            return x * x;
        }
    },
    China(86, "中国") {
        @Override
        public double calcSomething(double x) {
            return x + 100;
        }
    },
    Japan(81, "日本") {
        @Override
        public double calcSomething(double x) {
            return (x + 100) / x;
        }
    },
    (GermanyとTheUnitedKingdomは略)
    ;
    
    private int code;
    private String japaneseName;

    private Country (int code, String japaneseName) {
        this.code = code;
        this.japaneseName = japaneseName;
    }
    public int getCode(){
        return code;
    }
    public String getJapaneseName(){
        return japaneseName;
    }
    /**
    * abstract指定し、実態は各要素中で定義する
    */
    abstract public double calcSomething(double x);
}

各要素の後ろに{}を用意し、その中でabstractメソッドの実態を定義するところが少し変わっている。この応用例はいろいろあり、例えばEncodersというエンコードアルゴリズムをリストしたenumを用意して、各アルゴリズムごとにエンコードするメソッドを定義したりといったことが考えられる。また、コード例の中で@Overrideが使用されていることからもわかるが、通常のメソッドに対してOverrideすることも可能である。

public enum Country {

    America(1, "アメリカ"),
    China(86, "中国"),
    Japan(81, "日本") {
        /**
        * 日本のみ別の処理を実装する
        */
        @Override
        public double calcSomething(double x) {
            return (x + 100) / x;
        }
    },
    Germany(49, "ドイツ"),
    TheUnitedKingdom(44, "イギリス"),

    ;
    
    private int code;
    private String japaneseName;

    private Country (int code, String japaneseName) {
        this.code = code;
        this.japaneseName = japaneseName;
    }
    public int getCode(){
        return code;
    }
    public String getJapaneseName(){
        return japaneseName;
    }
    /**
    * アメリカと中国はデフォルトのメソッドを使用する
    */
    public double calcSomething(double x) {
        return x;
    }
}

メソッド呼び出しの例は以下の通り。

Country c = Country.Japan;
double y = c.calcSomething(100);

enumでinterfaceを実装する

enum自体が単なるabstract classであり、各要素がそれぞれ異なる実装であるならば、先ほどのcalcSomethingメソッドはinterfaceとして切り出せるのではないか?答えはYESであり、Javaenumはinterfaceをimplementsすることができる。

public interface Calculator {
    public double calcSomething(double x);
}

public enum Country implements Calculator {

    America(1, "アメリカ"),
    China(86, "中国"),
    Japan(81, "日本"),
    Germany(49, "ドイツ"),
    TheUnitedKingdom(44, "イギリス"),
    ;

    (略)

    /**
    * interfaceで定義されたメソッドを実装する
    */
    public double calcSomething(double x) {
        return x;
    }
}

実際の使われ方としてはenumのメソッドをインターフェースで切り出すというより、Comparableのようにすでに定義されているインターフェースの実装をenumに持たせるほうが実用的だろう。

まとめ

今回はJavaの列挙型enumの幾つかの使い方について解説した。参考にいろいろenumの実装をコーディングしてみてはいかがだろうか。

sponsor