怒Mは思いつきでモノを言う

やったことメモなどなど

Enum型を勉強してみた。

現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法を読んでいます。
「Chapter 2. 場合分けのロジックを整理する」でJavaの列挙型について書いてあったので調べてみました。

Enum型の使い方(1)

public enum MeatEnum {
    BEEF,
    PORK,
    CHICKEN,
    LAMB;
}

こんな感じで定数を定義します。 BEEFPORKなどは列挙子と呼びます。
Java言語仕様では "Enum Constant" と書かれていますね。 Enumは"enumeration" から来ているのですね。
「列挙」という意味の他に「一覧」とか「目録」という意味に訳せるようで、
プログラミング未経験の新人には、列挙型よりもEnum型を示す表現としては分かりやすい気がします。

public class Test {

    public static void main(String[] args) {

        String something = args[0];
        
        if(MeatEnum.BEEF.toString().equals(something)) {
            System.out.println("BEEF");            
        } else if(MeatEnum.PORK.toString().equals(something)) {
            System.out.println("PORK");            
        } else if(MeatEnum.CHIKEN.toString().equals(something)) {
            System.out.println("CHICKEN");            
        } else if(MeatEnum.LAMB.toString().equals(something)){
            System.out.println("LAMB");            
        } else {
            System.out.println("Something's Meat");
        }
    }
}

↑みたいにif文で使えますが、こういう使い方はしないですよね。

public class Test2 {

    public static void main(String[] args) {
        
        // 標準入力で BEEF/CHICKEN/PORK/LAMB が入力されレバー
        // 文字列に合致した定数で返してくれます。
        // 指定された文字列に合致しない場合は、IllegalArgumentExceptionが発生。
        MeatEnum meat = MeatEnum.valueOf(args[0]);
        
        // switch文だと以下のような感じで、シンプルに書けます。
        switch(meat) {
        case BEEF:
            System.out.println(meat);
            break;
        case PORK:
            System.out.println(meat);
            break;
        case CHICKEN:
            System.out.println(meat);
            break;
        case LAMB:
            System.out.println(meat);
            break;
        default:
            System.out.println("Something's Meat");
            break;
        }
    }
}

switch文で、こういう使い方は見ることがあります。
どちらも標準入力で適切な文字列を渡した場合は、どちらも同じ結果が出力されます。

Enum型の使い方(2)

public enum MeatEnumKai {
    BEEF(286),   //肩ロース、脂身付き
    PORK(386),   //豚バラ、脂身付き
    CHICKEN(200), //鶏モモ、皮付き
    LAMB;   //ラム肉、子羊の背肉

    // コンストラクタは private だけ宣言できます。
    // public, protected を指定するとコンパイルエラーとなります。
    private MeatEnumKai() {
        System.out.println("ラム肉は0Kcal!");
    }

    // 当然コンストラクタは複数定義できます。 
    private MeatEnumKai(int calorie) {
        this.calorie = calorie;
    }    

    private int calorie; // 単位:Kcal

    // Enum型はクラスなので、メソッドやフィールドを定義できます。
    public int getCalorie() {
        return calorie; 
    }
}

Enum型はクラスですので、コンストラクタもフィールドも、メソッドも定義できます。
Enum型には、その列挙子で定義されたもの以外のインスタンスは持ちません。
Enum型を明示的にインスタンス化するとコンパイルエラーとなります。

MeatEnumKai meat = new MeatEnumKai(); // コンパイルエラー

MeatEnumKaiを実際に使ってみます。

public class Test3 {

    public static void main(String[] args) {
        
        MeatEnumKai chicken = MeatEnumKai.CHICKEN;
        MeatEnumKai lamb = MeatEnumKai.LAMB;
        
        System.out.println("鶏肉は、" + chicken.getCalorie() + "Kcal");
        System.out.println("ラム肉は無敵の、" + lamb.getCalorie() + "Kcal");
    }
}
ラム肉は0Kcal!
鶏肉は、200Kcal
ラム肉は無敵の、0Kcal

コンストラクタやメソッドを定義することで、Enum型の使い方に幅ができますね。

Enum型の使い方(3)

「現場で役立つシステム設計の原則」に書いていた使い方です。
Enum型を使いこなせていなかったなーと反省です。

public interface MeatDish {
    public String cooking();
}

こんなインターフェースを用意します。
で、実装します。

public class BeefDish implements MeatDish {
    @Override
    public String cooking() {
        return "牛肩ロースの赤ワイン煮";
    }
}

public class PorkDish implements MeatDish {
    @Override
    public String cooking() {
        return "豚の生姜焼き";
    }
}

public class ChickenDish implements MeatDish {
    @Override
    public String cooking() {
        return "親子丼";
    }
}

public class LambDish implements MeatDish {
    @Override
    public String cooking() {
        return "ジンギスカン";
    }
}

肉料理を返すメソッドです。
これを使う、Enum型を定義します。

public enum MeatEnumHyper {
    BEEF ( new BeefDish() ),   //肩ロース、脂身付き
    PORK ( new PorkDish() ),   //豚バラ、脂身付き
    CHICKEN( new ChickenDish() ), //鶏モモ、皮付き
    LAMB ( new LambDish() );   //ラム肉、子羊の背肉

    private MeatDish dish;

    // BEEF,PORK,CHICKEN,LAMB のコンストラクタ
    private MeatEnumHyper(MeatDish dish) {
        this.dish = dish;
    }
    
    public String offerDish() {
        return dish.cooking(); 
    }
}

使ってみましょう。

public class Test4 {

    public static void main(String[] args) {

        // 標準入力で BEEF/CHIKEN/PORK/LAMB が入力されレバー
        MeatEnumHyper meat = MeatEnumHyper.valueOf(args[0]);

        System.out.println(meat.offerDish());
    }
}

標準入力で肉がちゃんと指定されれば、それぞれの肉料理が呼び出されます。
呼び出し側はMeatEnumHyperにマトンや鴨肉が追加されても意識することなく、
肉料理を表示することができます。

Enum型はもっと使いようがある

他にも、EnumSetEnumMapを使うことで、できることの幅ができます。
「現場で役立つシステム設計の原則」にもありますが、
Enum型を使うことによって、業務ロジックでよくある区分判断で複雑になりがちな、
if文やswitch文を簡素な表現にすることができそうです。