[勉強会便り] Java の 列挙型

|

かなり間が空いてしまいましたが、前回に引き続き、現在読んだいるテキスト「Effective Java 第2版」からの話題を取り上げたいと思います。

今回は Java の列挙型 (Enum) についてです。

前回のジェネリックスと同様に、列挙型も Java5 以降に追加されました。
Java5 がリリースされてから、もうずいぶん経ちますが、意外と列挙型をよく知らないという人も多いのではないでしょうか。

「Effective Java 第2版」で列挙型について解説されている項目は以下の通りです。

  • 項目30 int定数の代わりにenumを使用する
  • 項目31 序数の代わりにインスタンスフィールドを使用する
  • 項目32 ビットフィールドの代わりにEnumSetを使用する
  • 項目33 序数インデックスの代わりにEnumMapを使用する
  • 項目34 拡張可能なenumをインタフェースで模倣する

恥ずかしながら、この本を読むまで、Java の Enum がここまで強力なものとは知りませんでした。
Enum の使いどころや利点を紹介したいと思います。

Enumとは

C言語では、列挙型というと、「名前付きの整数定数を定義するもの」ということになると思います。

C# などの .NET の言語でも、基本的には enum の実体は int 値ですが、Java の enum はこれらとは異なります。

Java での enum はインスタンス数が制限されていることを実行環境が保証している本格的なクラスです。

Effective Java では、このことを「インスタンス制御されている」と記述しています。
別の言い方として、enum は「シングルトンを汎用化したもの」とも書かれています。
(シングルトンとは、デザインパターンの1つで、インスタンス数が1つであることを保証するクラス設計のパターンです。)

この説明だけでも、勘のよい人は Java の enum が非常に有用なものだと直感していただけると思います。

Effective Java では固定数の定数が必要な場合には、常に enum を使用すべきだと断言されています。

業務プログラムでよくある「~区分」というデータなどは、まさに enum を使用すべき場面なのではないかと思います。

以下では、本の中で挙げられている、具体的な使いどころを紹介します。
もちろん、詳しくは書籍を読んで下さい。

基本の使い方

public enum Sex {
  MALE, FEMALE
}

これで Sex クラスはインスタンスが MALE と FEMALE の2つしか存在しないクラスであることが保証されます。

Sex クラス型の変数に MALE と FEMALE 以外の値を入れることはできませんし、MALE を表す場合は常に同じインスタンスなので == で等価比較を行うことができます。

また、以下のようにすると、内部的に値を持たせることも可能ですし、インスタンスを適切に文字列化することやその逆も可能です。

public enum Sex {
  MALE("M", "MALE"), FEMALE("F", "FEMALE");

private final String shortName;
private final String longName;
Sex(String shortName, String longName) {
this.shortName = shortName;
this.longName = longName;
}
@Override
public String toString() {
return longName;
}
private static final Map stringToEnum
= new HashMap();
static {
for (Sex e : values()) {
stringToEnum.put(e.shortName, e);
}
}
public static Sex fromString(String shortName) {
return stringToEnum.get(shortName);
}
}

このように、Enum は完全なクラスなので、インスタンスフィールドを持たせることも、コンストラクタや static メソッド、インスタンスメソッドを定義することも可能です。

ただし、Enum は不変であるべきですので、インスタンスフィールドは final とするべきです。
コンストラクタは、Enum のクラス定義以外では呼び出せません。
(上記の例では Enum の定数定義を括弧付きで記述しているところでコンストラクタが呼び出されています。)

利用先(クライアント)のコードを再コンパイルすることなく、enum に定数を追加することも、定数の順序を変更することも可能です。これは int の定数で表す場合と比べて大きな利点です。

ビットフィールドの代替(EnumSet)

1つの変数で複数のフラグ等の状態を表したいときに、ビットフィールドと呼ばれる方法を利用することがあると思います。

// 古いコードの例
public class FlagSampleOld {
  public static final int FLAG_A = 1;
  public static final int FLAG_B = 2;
  public static final int FLAG_C = 4;
  public static final int FLAG_D = 8;

private int state = 0;
public void turnOn(int flag) {
state |= flag;
}
public void turnOff(int flag) {
state &= ~flag;
}
public boolean isOn(int flag) {
return (state & flag) != 0;
}
}

FlagSample flag = new FlagSample();
// A と B と C のフラグを ON
flag.turnOn(FLAG_A | FLAG_B | FLAG_C);
// A と C のフラグを OFF
flag.turnOff(FLAG_A | FLAG_C);
// A のフラグが立っているか
flag.isOn(FLAG_A);

上記のようなコードは、turnOn や turnOff に渡される値を保証できないことや、フラグを文字列化する適切な方法が無いことや、使用可能なフラグを列挙することなどができません。渡される値を保証できないことは予期しないバグの元になりますし、文字列化できないことはデバッグの時の解析を難しくします。

Enum と EnumSet というものを使用すると、こういった問題を解決できます。

public enum FlagEnum {
  A, B, C, D
}

Set flag = EnumSet.noneOf(FlagEnum.class);
// A と B と C のフラグを ON
flag.addAll(EnumSet.of(FlagEnum.A, FlagEnum.B, FlagEnum.C));
// フラグの状態を文字列化
flag.toString() // => [A, B, C]

// A と C のフラグを OFF
flag.removeAll(EnumSet.of(FlagEnum.A, FlagEnum.B));
// フラグの状態を文字列化
flag.toString() // => [C]

// A のフラグが立っているか
flag.contains(FlagEnum.A)

標準的な Java の Set のインターフェースのままで記述しているので、少し記述量が増えていますが、これで型安全なビットフィールドを表現することが可能です。

パフォーマンスについても、Enum定数の個数が64個以下の場合、EnumSet は内部の状態を long で表現するため、ビットフィールドのパフォーマンスと比べても遜色ないそうです。

振る舞いを持つ定数

すでに、メソッドの追加を行っているので Enum に振る舞いを持たせることができるのは分かると思いますが、それぞれの Enum 定数でメソッドをオーバーライドすることも可能です。

本の中に書いてある具体例を挙げます。

public enum Operation {
  PLUS("+") {
    double apply(double x, double y) { return x + y; }
  },
  MINUS("-") {
    double apply(double x, double y) { return x - y; }
  },
  TIMES("*") {
    double apply(double x, double y) { return x * y; }
  },
  DIVIDE("/") {
    double apply(double x, double y) { return x / y; }
  };
  private final String symbol;
  Operation(String symbol) {
    this.symbol = symbol;
  }
  @Override
  public String toString() {
    return symbol;
  }
  abstract double apply(double x, double y);
}

この定数ごとに振る舞いを持たせる Enum は、内部で使用するロジック(アルゴリズム)を切り替えることができる、ストラテジパターンというデザインパターンに利用することができます。

シングルトンパターン

これは、最初に挙げた enum の項目ではなく、下記の項目で説明されています。

  • 項目3 private のコンストラクタか enum 型でシングルトン特性を強制する

シングルトンとは、そのクラスのインスタンスが1つしかないことを保証するクラス設計のパターンですが、Java 1.4 までは、private なコンストラクタを用意して実装するのが一般的でした。

Java 1.5 以降では、シングルトンは下記のように、Enum を使用して実装するのがベストです。

public enum SingletonSample {
  INSTANCE;

public void xxx() { ... }
}

終わりに

Java の Enum は、他の言語での同様の概念から想像していた以上に便利だと思います。

この本で、その便利さを知ってから、業務のプログラムでもいくつか Enum を使用するように置き換えました。

初心者~中級者でありがちなのは、新しい概念を勉強すると、使い方をよく分からないうちに何でもそれを使おうとするので、結果的に使わない方がよいものまで使ってしまって困ったことになる、というのがありますが、Enum についてはどんどん使ってみてもよいのではないでしょうか?

(もちろんプロジェクトの他のメンバーも Enum を理解していることが前提になりますが。。。)

これからも、勉強会で出てきた話題の中から役立ちそうなものを紹介していきたいと思います。お楽しみに。

このブログ記事について

ひとつ前のブログ記事は「社員インタビュー 社長編 Vol.3」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。