入門Javaのenum

> この記事のサンプルコードは、enumの説明に特化しています。それゆえ、一般的には良くないとされるコードも含まれています（金額の計算で `BigDecimal` ではなく `int` や `double` を使っているなど）。

# 分類などをどう表すか
例えば、架空のECサイトのシステムを考えます。このECサイトの会員にはブロンズ会員・シルバー会員・ゴールド会員の３つのランクがあり、ランクによって割引率などが異なります。これをどうやって表しましょうか？

まずは良くない例です。

```java:良くない例
public class Rank {
    public static final int BRONZE = 1;
    public static final int SILVER = 2;
    public static final int GOLD = 3;
}
```

ランクを整数の定数で表しています。こうすると、どのような弊害があるでしょうか？

例えば、ランクを引数に取るメソッドがあるとしましょう。

```java:ランクを引数に取るメソッドの定義の例
public class PriceCalculator {
    public int getDiscountPrice(int price, int rank) {
        switch (rank) {
            case Rank.BRONZE:
                return price;
            case Rank.SILVER:
                return (int) (price * 0.9);
            case Rank.GOLD:
                return (int) (price * 0.8);
            default:
                throw new IllegalArgumentException("Invalid rank");
        }
    }
}
```

このメソッドの第２引数は、本来は `Rank.BRONZE` などを指定してほしいのでしょう。しかし、その旨を丁寧にJavadocに書いたとしても、それを読まないでこんな風に使う人がいるかもしれません。

```java:想定しない呼び出しの例
getDiscountPrice(10000, 1);  // 定数を使わずにハードコーディング
getDiscountPrice(10000, 0);  // 定数に定義されていない値を指定
```

また、ランクと割引率という非常に関連性の強い値が、別のクラスに記述されているのも気になります。後々で保守が大変そうです。

もし同じ `Rank` クラス内で割引率を定数化しても、ランクの定数と同じ現象（ハードコーディングなど）は起こりえます。それに、ランクにまつわる割引率以外の値が必要になったとき、更に同じような定数を増やすのは嫌ですよねえ・・・。

```java:良くない例
public class Rank {
    public static final int BRONZE = 1;
    public static final int SILVER = 2;
    public static final int GOLD = 3;
    
    // 定数がどんどん増えていく
    public static final double DISCOUNT_RATE_BRONZE = 0.0;
    public static final double DISCOUNT_RATE_SILVER = 0.1;
    public static final double DISCOUNT_RATE_GOLD = 0.2;
}
```

# enumとは
そこで登場するのがenumです。日本語では「列挙型」とも呼ばれます。

enumはこんなのです。

```java:enumの例
public enum Rank {
    // 定数
    BRONZE(0.0),
    SILVER(0.1),
    GOLD(0.2);

    // フィールド
    private final double discountRate;

    // コンストラクタ
    private Rank(double discountRate) {
        this.discountRate = discountRate;
    }

    // メソッド
    public int getDiscountPrice(int price) {
        return (int) (price * (1 - discountRate));
    }
}
```

enumは、次の特徴を持つ特殊なクラスです。

- フィールド・メソッドは普通のクラスと同様に定義できる
- コンストラクタは`private`にしかできない＝外部からのインスタンス生成は不可能
    - アクセス修飾子が無い場合は`private`と解釈される
    - `public`・`protected`を付けるとコンパイルエラー
- クラス直下に定数を宣言する
    - これらの定数は自クラスのインスタンス
    - 自クラスの`public static final`なフィールドとなる

つまり、`BRONZE`・`SILVER`・`GOLD`は`Rank`クラスのインスタンスであり、かつ`Rank`クラスの`public static final`なフィールドなのです。

`()`内に指定された値は、コンストラクタに渡す引数です。つまり今回の場合、コンストラクタを通して`discountRate`フィールドの値となります。

むりやりクラスとして書くと、こんなイメージです。

> 後述しますが、実際のenumとは異なります。あくまでイメージとして捉えてください。

```java:enumを無理やりクラスで書いた例
public class Rank {
    // 定数
    public static final Rank BRONZE = new Rank(0.0);
    public static final Rank SILVER = new Rank(0.1);
    public static final Rank GOLD = new Rank(0.2);

    // フィールド
    private final double discountRate;

    // コンストラクタ
    private Rank(double discountRate) {
        this.discountRate = discountRate;
    }

    // メソッド
    public int getDiscountPrice(int price) {
        return (int) (price * (1 - discountRate));
    }
}
```

# enumの何が嬉しいか
一言で言うと、前述の`int`定数の問題を解決できます。

ランクを引数に取るメソッドがあった場合、定義されていない値を指定したり、定数を使わずにハードコーディングしたりすることが不可能になります。

```java
// Rankに定義された定数（BRONZE・SILVER・GOLD）以外は引数に指定できない！
public void doSomething(Rank rank) {
    ...
}
```

> もちろん、`null`は引数に指定できてしまいます。これはJavaの文法上、どうにもならないですね・・・😅

また、ランクと割引率（`discountRate`）という非常に関連性の強い値を、1つのクラスにまとめることができます。これにより、割引率を使うメソッド（`getDiscountPrice()`）も同クラス内に定義できます。保守が楽チンですね！

更なるenumの活用術については、次の書籍を読むと良いでしょう。

- [現場で役立つシステム設計の原則](https://gihyo.jp/book/2017/978-4-7741-9087-7)
- [Software Design 2021年3月号の特集「Javaでもう一度学び直すオブジェクト指向プログラミング」](https://gihyo.jp/magazine/SD/archive/2021/202103)

# [上級編]各定数ごとに処理を変えて、使う側の条件分岐を減らす

```java:enumを使う側の良くない例
public double calc(double x, double y, Operation ope) {
    switch(ope) {
    case PLUS:
        return x + y;
    case MINUS:
        return x - y;
    case TIMES:
        return x * y;
    case DIVIDED_BY:
        return x / y;
    }
}
```

このような条件分岐内のロジックは、enumの各定数に持たせましょう。そうすると、使う側の条件分岐を消すことができます。

```java:enumを使う側の良い例
public double calc(double x, double y, Operation ope) {
    return ope.eval(x, y);
}
```

## 方法① 各定数ごとにメソッドをオーバーライドする

> このコードはJava Language Specificationから拝借し、少し変更を加えたものです（ URL→ https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#d5e15436 ）


```java
enum Operation {
    PLUS {
        @Override
        double eval(double x, double y) {
            return x + y;
        }
    },
    MINUS {
        @Override
        double eval(double x, double y) {
            return x - y;
        }
    },
    TIMES {
        @Override
        double eval(double x, double y) {
            return x * y;
        }
    },
    DIVIDED_BY {
        @Override
        double eval(double x, double y) {
            return x / y;
        }
    };

    abstract double eval(double x, double y);
}
```

> 文法的には無名クラスの書き方です。

## 方法② インタフェースを組み合わせる
ロジックが長い場合はこちらの方がいいかもしれません。

```java
interface Calculator {
    double eval(double x, double y);
}

class PlusCalculator implements Calculator {
    @Override
    public double eval(double x, double y) {
        return x + y;
    }
}

class MinusCalculator implements Calculator {
    @Override
    public double eval(double x, double y) {
        return x - y;
    }
}

class TimesCalculator implements Calculator {
    @Override
    public double eval(double x, double y) {
        return x * y;
    }
}

class DivideCalculator implements Calculator {
    @Override
    public double eval(double x, double y) {
        return x / y;
    }
}
```

```java
enum Operation {
    PLUS(new PlusCalculator()),
    MINUS(new MinusCalculator()),
    TIMES(new TimesCalculator()),
    DIVIDED_BY(new DivideCalculator());
    
    private final Calculator calculator;

    Operation(Calculator calculator) {
        this.calculator = calculator
    }

    double eval(double x, double y) {
        return calculator.eval(x, y);
    }
}
```

# [上級編]enumはEnumのサブクラス
enumは暗黙的に`java.lang.Enum`のサブクラスとして定義されます。ということは、`Enum`クラスのメソッドを全て使えるということです。

`Enum`のメソッド一覧 → https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Enum.html

ちなみに、型パラメータ`E`には定義したenum自身が入ります。つまりこんな感じです。

```java:enumをクラスで書いたイメージコード
public class Rank extends Enum<Rank> {
    ...
}
```

`Enum`で定義されたメソッドの中で、特に重要なものは次のとおりです。

| メソッド | 説明 |
|-------|-----|
| `String name()` | 定数名を返す |
| `String toString()` | 定数名を返す |
| `int ordinal()` | 定数が宣言された順番を返す（最初の定数が`0`） |

また、enum定義時に自動的に作成されるメソッドもあります。

| メソッド | 説明 |
|-------|-----|
| `static <T extends Enum<T>> T[] values()` | 全定数の配列を返す（順番は宣言されたとおり） |
| `static <T extends Enum<T>> T valueOf(String name)` | 引数で指定された名前（完全一致）の定数を返す |

```java:利用例
Rank[] ranks = Rank.values();
Rank rank = Rank.valueOf("GOLD");  // Rank.GOLDが返る
```

# [上級編]EnumSetの利用
`java.util.EnumSet`クラスは、名前の通りenumのSetです。

例えば、シルバー会員とゴールド会員のみプレゼントを受け取れるとしましょう。これを判定するメソッドはどう実装するでしょうか？

```java:良くない例
public boolean canGetPresent(Rank rank) {
    return rank == Rank.GOLD || rank == Rank.SILVER;
}
```

この規模だと悪くない気もします。しかし、ランクの数が全部で10個になって、プレゼントを受け取れるランクが5個になったらどうでしょうか？書くのも読むのも辛いですね。

そんなときに`EnumSet`を使います。

```java:EnumSetを使った例
import java.util.EnumSet;

public enum Rank {
    BRONZE(1.0),
    SILVER(0.9),
    GOLD(0.8);

    // フィールド等省略

    // EnumSetを利用！
    private static final EnumSet<Rank> ranksCanGetPresent = EnumSet.of(SILVER, GOLD);

    public boolean canGetPresent() {
        // EnumSet#contains()を利用
        return ranksCanGetPresent.contains(this);
    }
}
```

```java:利用側のコード例
Rank rank = ...;
if (rank.canGetPresent()) {
    System.out.println("プレゼントをどうぞ！");
}
```

これなら、ランクの数が増えても簡単ですね！