6.37:序数インデックスの代わりに EnumMap を使う
要点
列挙型(enum)をキーにして何かを管理するときは、ordinal() を使って配列/序数インデックスで管理するより EnumMap<YourEnum, V> を使う — 型安全で可読性が上がり、内部実装は効率的(実質的に配列ベース)なので性能面でも優れる。
なぜ EnumMap を使うべきか
-
型安全・可読性:
map.get(Planet.EARTH)の方がarr[Planet.EARTH.ordinal()]より読みやすく、間違えにくい。 -
実装効率が高い:
EnumMapは内部的にenum.ordinal()をベースに配列で実装されており、HashMapより軽量で高速(O(1))。 -
保守性:
ordinal()に直接依存するコードは enum の並べ替えで壊れるが、EnumMapならキーは enum 値そのものなので順序変更の影響を受けない(※永続化は別途考える)。 -
標準 API の利点:
EnumMapはMapの API を持つため、for-each/stream/Collections等と自然に使える。 -
null キー禁止:
EnumMapはnullキーを受け付けない → キーの欠如を早期に検出できる。
悪い例
序数インデックス/配列で管理するパターン
// 悪い:ordinal() に依存した配列でデータを管理
double[] gravity = new double[Planet.values().length];
gravity[Planet.EARTH.ordinal()] = 9.8;
double g = gravity[Planet.MARS.ordinal()];
問題点:
-
Planetの定義順を変えたり列挙子を挿入するとデータの意味が壊れる。 -
ordinal()を永続化や外部フォーマットで使うと互換性が壊れやすい。 - 配列だったらキーとして何の enum を使っているか一目でわかりにくい(可読性低下)。
良い例
EnumMap を使うパターン
enum Planet { MERCURY, VENUS, EARTH, MARS }
EnumMap<Planet, Double> gravity = new EnumMap<>(Planet.class);
gravity.put(Planet.EARTH, 9.8);
double g = gravity.get(Planet.MARS); // null チェックが必要(存在保証がないため)
利点:
-
gravity.get(Planet.MARS)と書けば何を参照しているか即座に分かる。 - enum の宣言順を変えても map のキーはそのままなので壊れない。
- 内部は配列ベースなので高速。
★ただし、get時にnullチェックは忘れずに行う。
初期化・変換の小技
配列や既存データから EnumMap に変換するパターン:
// 全要素を既定値で初期化
EnumMap<Planet, Double> gravity = new EnumMap<>(Planet.class);
for (Planet p : Planet.values()) gravity.put(p, defaultValue);
// 既存の配列から変換する(移行用)
double[] arr = ...;
EnumMap<Planet, Double> map = new EnumMap<>(Planet.class);
Planet[] vals = Planet.values();
for (int i = 0; i < vals.length && i < arr.length; i++) {
map.put(vals[i], arr[i]);
}
EnumMap と HashMap の比較
-
EnumMapは enum のordinal()を内部的に使うため 配列アクセス並みに高速でメモリ効率も良い。 -
HashMap<Enum, V>より小さく高速(ハッシュ計算やバケット操作が不要に近い)。
※ただしEnumMapはキーが同じ enum 型で固定される場面にのみ使える(汎用性はHashMapの方が高い)。
注意点 / 実務上のポイント
-
null キー不可:
EnumMapは null をキーにできない(NullPointerException)。null をキーで表す設計は避ける。 -
値が存在しない場合の扱い:
get()が null を返す可能性があるので、必ずcontainsKey/getOrDefault/Objects.requireNonNull等で扱いを決める。 -
スレッドセーフではない:
EnumMap 自体はスレッドセーフではない。並行利用ならCollections.synchronizedMap(new EnumMap<>(...))やConcurrentHashMapを検討。 -
永続化:
永続化や外部フォーマットでordinal()を使わない。DB カラムで enum を表現するならname()や明示的なコードフィールドを使い、fromCodeなどで復元する。 -
大きな enum:
enum の要素数が極端に大きい場合はEnumMapの配列サイズが大きくなるので設計を見直す(通常は問題にならない)。
まとめ
- Enum をキーにしたデータ管理は 直接 ordinal インデックスで配列管理しないで、EnumMap を使う。
-
EnumMapは読みやすく安全、かつ内部的に効率的(配列ベース)なのでパフォーマンス面でも優れる。 - 例外:キーが enum 型でない/並列性や特殊要件があるときは別のデータ構造を検討する。