enumについて

##enumとは
列挙型。列挙クラス。
内部的には、java.lang.Enumクラスを継承したクラス。[^1]

[^1]: java.lang.Enumの継承はコンパイラによって行われる。プログラマが明示的にクラスを継承することはできない。

なお、enumについては書籍『[Effective Java](https://www.amazon.co.jp/EFFECTIVE-JAVA-Java-Joshua-Bloch/dp/4621066056)』の第６章が詳しい。
著者のJoshua Blochは[JSR 201](https://jcp.org/en/jsr/detail?id=201)のスペックリードである。

##enumが解決する問題
###enum以前
当初、区別することのみを目的とした定数宣言はint enumパターン[^2]で記述されていた。

```java:CalendarConst.java
public final static int JANUARY = 0;
public final static int FEBRUARY = 1;
public final static int MARCH = 2;
public final static int APRIL = 3;
public final static int MAY = 4;
public final static int JUNE = 5;
public final static int JULY = 6;
public final static int AUGUST = 7;
public final static int SEPTEMBER = 8;
public final static int OCTOBER = 9;
public final static int NOVEMBER = 10;
public final static int DECEMBER = 11;
public final static int UNDECIMBER = 12;
```

int enumパターンは、マジックナンバーを出現させないことにおいて有効だったが、以下のような問題があった。

１）型保証なし
intであるため、必要な場所で無関係なint値を渡したり、2つの定数を足し合わせるなど意味のない記述ができる。

２）名前空間がない
定数に文字列を接頭辞として追加し、その他のint列挙型と競合しないようにしなければならない。

３）脆弱性
int列挙はコンパイル時定数である。新規定数が既存の2定数間に追加されたり、順番が変更になると、クライアントを再コンパイルしなければならない。コンパイルしないと、実行は可能だが、動作は予測できない。

４）出力値に情報価値がない
出力はすべて数値になる。意味がわからず、その型が何であるかもわからない。

[^2]: 現在はアンチパターンである。

###タイプセーフenumパターン
int enumパターンの問題を解決したのがタイプセーフenumパターンである。
列挙定数をひとつのSingletonクラスで提供する。

```java:Month.java
public class Month {

    private final String name;
    private final int ordinal;

    private Month(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

    public String name() {
        return name;
    }

    public int getValue()
    {
        return ordinal() + 1;
    }

    public String toString() {
        return name;
    }

    public static final Month JANUARY = new Month("JANUARY", 0);
    public static final Month FEBRUARY = new Month("FEBRUARY", 1);
    public static final Month MARCH = new Month("MARCH", 2);
    public static final Month APRIL = new Month("APRIL", 3);
    public static final Month MAY = new Month("MAY", 4);
    public static final Month JUNE = new Month("JUNE", 5);
    public static final Month JULY = new Month("JULY", 6);
    public static final Month AUGUST = new Month("AUGUST", 7);
    public static final Month SEPTEMBER = new Month("SEPTEMBER", 8);
    public static final Month OCTOBER = new Month("OCTOBER", 9);
    public static final Month NOVEMBER = new Month("NOVEMBER", 10);
    public static final Month DECEMBER = new Month("DECEMBER", 11);
}
```

これがenumが登場する以前（Java5以前）の標準的な記述方法である。

##enum
Java5以降ではenumクラス（列挙型）を使用することができる。

```java:Month.java

public enum Month {

    JANUARY,
    FEBRUARY,
    MARCH,
    APRIL,
    MAY,
    JUNE,
    JULY,
    AUGUST,
    SEPTEMBER,
    OCTOBER,
    NOVEMBER,
    DECEMBER;

    public int getValue() {
        return ordinal() + 1;
    }

}
```

簡潔な記述だが、原理的にはタイプセーフenumパターンを採用し、拡張させたものである。
タイプセーフenumパターンが適用されていることは、enumクラスをコンパイルしたclassファイルを逆コンパイルすることで確認できる。
逆コンパイル結果を以下に示す。

```java:Month.jad 
public final class Month extends Enum
{

    private Month(String s, int i)
    {
        super(s, i);
    }

    public int getValue()
    {
        return ordinal() + 1;
    }

    public static Month[] values()
    {
        Month amonth[];
        int i;
        Month amonth1[];
        System.arraycopy(amonth = ENUM$VALUES, 0, amonth1 = new Month[i = amonth.length], 0, i);
        return amonth1;
    }

    public static Month valueOf(String s)
    {
        return (Month)Enum.valueOf(g06_enum/Month, s);
    }

    public static final Month JANUARY;
    public static final Month FEBRUARY;
    public static final Month MARCH;
    public static final Month APRIL;
    public static final Month MAY;
    public static final Month JUNE;
    public static final Month JULY;
    public static final Month AUGUST;
    public static final Month SEPTEMBER;
    public static final Month OCTOBER;
    public static final Month NOVEMBER;
    public static final Month DECEMBER;
    private static final Month ENUM$VALUES[];

    static 
    {
        JANUARY = new Month("JANUARY", 0);
        FEBRUARY = new Month("FEBRUARY", 1);
        MARCH = new Month("MARCH", 2);
        APRIL = new Month("APRIL", 3);
        MAY = new Month("MAY", 4);
        JUNE = new Month("JUNE", 5);
        JULY = new Month("JULY", 6);
        AUGUST = new Month("AUGUST", 7);
        SEPTEMBER = new Month("SEPTEMBER", 8);
        OCTOBER = new Month("OCTOBER", 9);
        NOVEMBER = new Month("NOVEMBER", 10);
        DECEMBER = new Month("DECEMBER", 11);
        ENUM$VALUES = (new Month[] {
            JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, 
            NOVEMBER, DECEMBER
        });
    }
}
```
タイプセーフenumパターンを適用し、Enumクラスを継承したSingletonクラスである。
valuesおよびvalueOfメソッド、ENUM$VALUESなどはコンパイラにより自動生成される。[^3]

[^3]: 先に自作したgetValueはインスタンスメソッドだが、valuesおよびvalueOfはMonthクラスのクラスメソッドである。


##enumでできること

###引数ありコンストラクタの定義

```java:Month.java
public enum Month {

    JANUARY(1),
    FEBRUARY(2),
    MARCH(3),

    private int monthNumber;

    private Month(int param) {
        monthNumber = param;
    }
}
```
以下のようにコンパイルされる。

```java:Month.jad
public final class Month extends Enum
{
    private Month(String s, int i, int param)
    {
        super(s, i);
        monthNumber = param;
    }

    public static Month[] values(){/*省略*/}

    public static Month valueOf(String s){/*省略*/}

    public static final Month JANUARY;
    public static final Month FEBRUARY;
    public static final Month MARCH;
    private int monthNumber;
    private static final Month ENUM$VALUES[];

    static 
    {
        JANUARY = new Month("JANUARY", 0, 1);
        FEBRUARY = new Month("FEBRUARY", 1, 2);
        MARCH = new Month("MARCH", 2, 3);
        ENUM$VALUES = (new Month[] {
            JANUARY, FEBRUARY, MARCH
        });
    }
}
```

###Enumクラスの利用
java.lang.Enumクラスと継承関係にあるため、[EnumのAPI](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Enum.html)を利用できる。