30 Nov 2014, 12:21

Effective Java にのっている エレガントな Enum の使い方メモ

はじめに

Effective Java には毎回驚かされる.

Enum とは

プログラマが選んだ各々の識別子をそのまま有限集合として持つ抽象データ型.

番号を持たないカテゴリ変数. 一意の文字定数.

実行時には, 番号が振られることが覆いが, 言語によっては番号はプログラマに見えないこともある.

int enum パターン

名前つき int 定数のグループを宣言すること.バッドノウハウ.

[sourcecode language=”java” title=””]
public static final int FOO = 0;
public static final int BAR = 1;
[/sourcecode]

  • コンパイラによる型検査の恩恵を受けることができない.
  • 同じ名前がついたものを名前空間で区別することができない.
  • 変更により再コンパイルが必要.
  • 表示可能な文字列へ変換する方法かない.
  • int と enum では実効速度はそれほどかわらない.

定数固有メソッド実装 (constant-specific method implementation)

enum 定数に対して振る舞いを関連付けるための方法.

パターン適用前.

[sourcecode language=”java” title=””]
public enum Operation {
PLUS, MINUS;

double apply (double x, double y) {
switch (this) {
case PLUS: return x + y;
case MINUS: return x – y;
}
throw new AssertionError () ("Unknown op:" + this);
}
}
[/sourcecode]

パターン適用後. enum 型で抽象メソッドを宣言して, 定数固有クラス本体で, 定数ごとに具象メソッドで その抽象メソッドをオーバーライド.

switch 文を排除するので, エレガント!! 抽象メソッドによって実装をカプセル化.

[sourcecode language=”java” title=””]
public enum Operation {
PLUS { double apply (double x, double y) {return x + y;} },
MINUS { double apply (double x, double y) {return x – y;} };

abstract double apply (double x, double y);
}
[/sourcecode]

定数固有クラス

さらに, 定数固有データと実装を組み合わせることで, 強力な表現力を.

[sourcecode language=”java” title=””]
public enum Operation {
PLUS ("+") { double apply (double x, double y) {return x + y;} },
MINUS ("-") { 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);
}
[/sourcecode]

Enum の toString は定数表現は 文字列へ変換することもできる.

戦略 Enum (Strategy Enum)

抽象メソッドをクラスに変更して外部から与えてやるようにすれば, これはいわゆる Strategy Pattern だ.

評価戦略を外部から与えて, Operation は委譲で評価をする.

[sourcecode language=”java” title=””]
public enum Operation {
PLUS (EvaluateType.PLUS), MINUS (EvaluateType.MINUS);

private final EvaluateType type;

Operation (EvaluateType type) {
this.type = type;
}

double apply (double x, double y) {
return type.apply (x, y);
}

// Strategy Enum Type
private enum EvaluateType {
PLUS { double apply (double x, double y) {return x + y;} },
MINUS { double apply (double x, double y) {return x – y;} };

abstract double apply (double x, double y);
}
}
[/sourcecode]

enum 定数と値の関連付けに ordinal をつかわないこと

enum と関連付けられた int 値を取得する メソッドとして ordinal メソッドがある.

これを定数と値を関連付けるときには, 使わない. なぜなら, コードの修正で, 振られる番号が変わるから.

[sourcecode language=”java” title=””]
public enum Number {
ONE, TWE;
public int getNumber{ return ordinal () + 1; }
}
[/sourcecode]

代わりにインスタンスフィールドを利用すればよい.

[sourcecode language=”java” title=””]
public enum Number {
ONE (1), TWE (2);
private final int number;
Number (int number) { this.number = number;}
public int getNumber{ return number; }
}
[/sourcecode]

集合と集合の対応づけに序数インデックス (配列) をつかわない

2 つの集合を対応付けるときには, 配列をつかうよりもいい方法がある.

それは, EnumMap. EnumMap は内部実装は配列でされているものの, インデックスを意識する必要がないというメリットがある.

配列をインデックスするために序数を使用することが適切であることはほ とんどない.代わりに, EnumMap を使用すること.

関連が多次元ならば, EnumMap<…, EnumMap<…>> というように連なっていく.

[sourcecode language=”java” title=””]
Map> herbsByType =
new EnumMap>(Herb.Type.class);
for (Herb.Type t : Herb.Type.values ())
herbsByType.put (t, new HashSet());
for (herb h: garden)
herbsBytpe.get (h.type).add (h);
[/sourcecode]

Enum シングルトンパターン

Enum を利用して, シングルトンパターンをする方法.

[sourcecode language=”java” title=””]
class SampleSingleton {
static public enum EnumUtil {
INSTANCE;

public static int plus (int x, int y) { return x + y; }
public static int minus (int x, int y) { return x – y; }
}

public static void main (String[] args) {
System.out.println (EnumUtil.INSTANCE.plus (1,1));
System.out.println (EnumUtil.INSTANCE.minus (2,1));
}
}
[/sourcecode]