LoginSignup
28
27

More than 3 years have passed since last update.

enumについて

Last updated at Posted at 2017-03-15

enumとは

列挙型。列挙クラス。
内部的には、java.lang.Enumクラスを継承したクラス。1

なお、enumについては書籍『Effective Java』の第6章が詳しい。
著者のJoshua BlochはJSR 201のスペックリードである。

enumが解決する問題

enum以前

当初、区別することのみを目的とした定数宣言はint enumパターン2で記述されていた。

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パターンは、マジックナンバーを出現させないことにおいて有効だったが、以下のような問題があった。

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

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

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

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

タイプセーフenumパターン

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

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クラス(列挙型)を使用することができる。

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ファイルを逆コンパイルすることで確認できる。
逆コンパイル結果を以下に示す。

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

enumでできること

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

Month.java
public enum Month {

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

    private int monthNumber;

    private Month(int param) {
        monthNumber = param;
    }
}

以下のようにコンパイルされる。

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を利用できる。


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

  2. 現在はアンチパターンである。 

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

28
27
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
27