Javaでenum(列挙型)の使い方

皆さんはJavaのenum(列挙型)を使っているでしょうか?
Javaのenumの実体はクラスであり、C#に比べて実装量が多くなるため正直言って使いにくいです。
そこで、なるべく実装量を減らして簡単に定義できるようにしてみましょう。

一般的なenumの定義方法

Javaでenumを定義する場合、列挙型の定義に加えて、数値を列挙型に変換するメソッドも実装する必要があります。
例えば、何も考えないで性別の列挙型を実装すると以下のようなコードになります。

public enum GenderEnum {
  MALE(1),
  FEMALE(2),
  OTHER(3);

  // Getterのみ定義する
  // 型はString等でも良い
  @Getter
  private int value;

  // コンストラクタ
  private GenderEnum(int value) {
    this.value = value;
  }

  // 数値を列挙型に変換するメソッド(列挙型毎に実装する)
  public static GenderEnum toEnum(int value) {
    for (var enumValue : GenderEnum.values()) {
      if (enumValue.getValue() == value) {
        return enumValue;
      }
    }
    return null;
  }
}

このように列挙型を定義すると、列挙型同士での比較、値の取得、値から列挙型への変換等が実現できます。
特に、Javaの場合はDBモデルに列挙型を適用するのが手間であるため、値から列挙型への変換は必須となります。

// 列挙型で比較
GenderEnum gender = GenderEnum.MALE;
if (gender == GenderEnum.MALE) {
}
// 数値の取得
int value = gender.getValue();
// 数値で比較
if (1 == GenderEnum.MALE.getValue()) {
}
// 数値から列挙型を取得
GenderEnum gender2 = GenderEnum.toEnum(value);

便利ですが、前述の実装だと値から列挙型への変換を全ての列挙型で定義しなくてはならず煩雑です。
また、Javaのenumはただのクラスなので、ファイル名と列挙型の名前を同じにして、列挙型の数だけjavaファイルを作成しなくてはなりません。
そこで、もう少し実装量を減らせないか工夫してみましょう。

内部クラス化する

列挙型は一つのJavaファイル内でまとめて定義したくなります。
そこで、内部クラスを使用します。
内部クラスを使うと一つのJavaファイルに複数の列挙型をまとめて定義できるようになります。
なお、今回の用途だと内部クラスはstaticで定義する必要がありますが、enumは暗黙的にstaticなので指定は不要です。

public class Enums {
  // 列挙型を内部クラスとして定義
  public static enum GenderEnum {
    MALE(1),
    FEMALE(2),
    OTHER(3);

    @Getter
    private int value;

    private GenderEnum(int value) {
      this.value = value;
    }

    public static GenderEnum toEnum(int value) {
      for (var enumValue : GenderEnum.values()) {
        if (enumValue.getValue() == value) {
          return enumValue;
        }
      }
      return null;
    }
  }

  // 内部クラスであれば複数の列挙型を同一ファイル内に定義できる
  public enum PrefectureEnum {
    HOKKAIDO(1),
    AOMORI(2),
…
    @Getter
    private int value;

    private PrefectureEnum(int value) {
      this.value = value;
    }

    public static PrefectureEnum toEnum(int value) {
      for (var enumValue : PrefectureEnum.values()) {
        if (enumValue.getValue() == value) {
          return enumValue;
        }
      }
      return null;
    }
  }
}

内部クラス化した場合の使い方は以下のようになります。

// 列挙型での比較(親クラス指定が必要)
Enums.GenderEnum gender = Enums.GenderEnum.MALE;
if (gender == Enums.GenderEnum.MALE) {
}
// 数値の取得
int value = gender.getValue();
// 数値で比較(親クラス指定が必要)
if (1 == Enums.GenderEnum.MALE.getValue()) {
}
// 数値から列挙型の取得(親クラス指定が必要)
Enums.GenderEnum gender2 = Enums.GenderEnum.toEnum(value);

列挙型の定義は1ファイルにまとまりますが、使う際に親クラスも指定する必要があるので面倒ですね。
そこで、親クラスの記述を省きたい場合は内部クラスをimportしてしまいましょう。
内部クラス化する前と同じ記述で使用できるようになります。

// 内部クラスをインポート
import sample.Enums.GenderEnum;
…
// 列挙型での比較(親クラスの指定が不要になる)
GenderEnum gender = GenderEnum.MALE;
if (gender == Enums.GenderEnum.MALE) {
}
// 数値の取得
int value = gender.getValue();
// 数値で比較(親クラスの指定が不要になる)
if (1 == GenderEnum.MALE.getValue()) {
}
// 数値から列挙型の取得(親クラスの指定が不要になる)
GenderEnum gender2 = GenderEnum.toEnum(value);

ジェネリクスで共通化

内部クラスで一つのファイルにまとまるようにはなりましたが、数値から列挙型を取得するメソッドを毎回定義する必要があるため煩雑です。
そこで、ジェネリクスを使ってもう少し定義を簡素化してみましょう。
※注意※
ジェネリクスを使うとint等のプリミティブ型は使えずInteger等の参照型を使うことになり、ジェネリクスを使った共通化は実施しない方が使いやすいかもしれません。

JavaのEnumは継承が使えないので、共通の親クラスとしてはインタフェースを利用します。
JDK1.8以降では、インタフェースに対してdefaultやstaticメソッドが定義できます。
ここではstaticメソッドで数値から列挙型への変換を実装し、子クラス(実際の列挙型)では型情報を指定します。

public class Enums {
  // 共通定義としてインタフェースを定義
  public interface EnumBase<E extends Enum<E>, V> {
    public V getValue();

    // 数値から列挙型への変換で任意の型に対応するメソッドとして実装
    // 型変換の警告が出るのでuncheckedで警告を除去
    @SuppressWarnings("unchecked")
    static <E extends Enum<E>, V> E getEnum(Class<E> enumClass, V value) {
      for (var b : (EnumBase<E, V>[])enumClass.getEnumConstants()) {
        if (b.getValue().equals(value)) {
          return (E)b;
        }
      }
      return null;
    }
  }

  // インタフェースを実装し、自身の列挙型を指定する
  public enum GenderEnum implements EnumBase<GenderEnum, Integer> {
    MALE(1),
    FEMALE(2),
    OTHER(3);

    // ジェネリクスでは参照型を使う
    @Getter
    private Integer value;

    private GenderEnum(Integer value) {
      this.value = value;
    }

    public static GenderEnum toEnum(Integer value) {
      // インタフェースのメソッドを呼び出す
      return EnumBase.getEnum(GenderEnum.class, value);
    }
  }
}

列挙型の値は参照型を使うことになりますが、使い勝手は前述と同じで変わりはありません。
プリミティブ型との比較においてはequalsメソッドを使わなくても==で比較できます。
※注意※
参照型をnewした場合は==での一致判定はインスタンスの一致判定になるので正常に比較できませんが、new Integer( )等は非推奨となっているので実質は問題ないと思います。
ただ、静的コード解析ツール等ではequalsメソッドを使うように修正を求められます。

import sample.Enums.GenderEnum;
…
// 列挙型での比較
GenderEnum gender = GenderEnum.MALE;
if (gender == Enums.GenderEnum.MALE) {
}
// 数値の取得
int value = gender.getValue();
// 数値で比較(equalsで比較する必要はない)
if (1 == GenderEnum.MALE.getValue()) {
}
// 数値から列挙型の取得(プリミティブ型を使える)
GenderEnum gender2 = GenderEnum.toEnum(value);

さて、Javaで列挙型を使う気になっていただけたでしょうか?
C#よりは大変なままですが、少しは実装が減ると良いですね。

Javaの最新記事8件