6.34:int定数の代わりにenumを使う
要点
int 定数(public static final int ...)を列挙的に表すなら enum を使う。enum のほうが型安全・可読性・拡張性・シリアライズの安全性で優れる。
なぜ enum が良いのか
-
型安全:
int はどんな値でも代入できるが、enum は定義した列挙子しか取り得ないので、誤った値を渡すミスをコンパイル時に防げる。 -
名前空間の管理:
列挙子はその enum 型のスコープに属するため名前衝突やグローバルな定数の氾濫を防げる。 -
振る舞いを持てる:
enum はフィールド・メソッドを持てる。値ごとの挙動(抽象メソッドをオーバーライド)も可能。 -
デバッグしやすい・可読性が高い:
toString() や名前がそのまま出るのでログやデバッグ時に直感的。 -
シリアライズが安全:
enum は名前でシリアライズされるため、通常の Serializable よりも安全で、複製されることはない(シングルトン性が守られる)。 -
コレクションとの親和性:
EnumSet / EnumMap など専用高速コレクションがある(ビット演算より安全で効率的)。 -
switch と相性が良い:
switch(enum) は可読で扱いやすい(Java ではサポートあり)。
悪い例
古い int 定数パターン
public class Planet {
public static final int MERCURY = 0;
public static final int VENUS = 1;
public static final int EARTH = 2;
// ... 多数の定数
}
// 呼び出し側
void travel(int planet) { ... }
travel(2); // 何の 2 か分かりにくい、誤値を渡せてしまう
問題点:
travel(999) や travel(-1) をコンパイルは通してしまう。ログに出るのは数値のみで意味が分かりにくい。
良い例
enum を使う
public enum Planet {
MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE;
}
// 呼び出し側
void travel(Planet planet) { ... }
travel(Planet.EARTH); // 型安全で可読性良し
さらにフィールドやメソッドを追加できる:
public enum Planet {
MERCURY(3.30e+23, 2.4397e6),
VENUS (4.87e+24, 6.0518e6),
EARTH (5.97e+24, 6.37814e6);
// ...
private final double mass; // kg
private final double radius; // meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
public double surfaceGravity() { /* 計算 */ }
@Override public String toString() { return name().toLowerCase(); }
}
→ データと振る舞いをひとまとめにできる。
EnumSet / EnumMap を使おう(ビットマスクの代替)
-
よくあるパターン:
複数選択のフラグを int のビットマスクでやるケース。enum + EnumSet の方が読みやすく安全。
public enum Permission { READ, WRITE, EXECUTE }
EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.EXECUTE)) { ... }
メリット: ビット演算より安全で、可読性も高く、EnumSet は内部的にビット集合に最適化されていて高速。
注意点・ベストプラクティス
-
ordinal() を永続化に使うな:
enum の ordinal()(定義順の整数値)は実装に依存するし、後で順序を変えたら壊れる。永続化や外部 API とやり取りする時は明示的なコード(フィールド)を持たせ、fromCode(int) のような変換メソッドを提供する。
public enum Status {
NEW(0), IN_PROGRESS(1), DONE(2);
private final int code;
Status(int code) { this.code = code; }
public int code() { return code; }
public static Status fromCode(int code) { /* map lookup */ }
}
-
外部との互換性(プロトコル・DB):
既に int を使って外部に流している場合は、enum に変換しても内部で int code を保持し、fromCode を用意するとスムーズ。 -
シリアライズ:
enum は Java のシリアライズで名前ベースに扱われるため比較的安全。ただし名前を変更すると互換性が壊れるので注意(DB 等に文字列を保存する場合も同様)。 -
パフォーマンスは心配不要:
enum は軽量で、通常パフォーマンス問題にはならない。 -
switch 文で扱うとき:
switch(enum) は推奨される使い方。枚挙される全ケースをコンパイル時に把握できる。
まとめ
- 列挙的な選択肢を表すなら enum を第一選択 に。
- int 定数は可読性・安全性の面で劣る。
- 複数選択フラグなら EnumSet を使う。
- 永続化・外部 API とのやり取りがあるなら ordinal() ではなく明示的なコードフィールドを使って変換する。