名前付きの値の固定された集合
値の中には、小さく既知のリストのいずれかとしてしか意味を持たないものがあります。曜日、トランプのスート、注文が取りうる状態などです。これらを int コード(0 は保留、1 は発送済み)や文字列("PENDING"、"SHIPPED")でモデル化することもできますが、呼び出し側が 7 や "PNEDING" を渡すのを止めるものは何もありません。enum はその穴を塞ぎます。型そのものが、あなたが宣言した値しか許可しないのです。
Day はまったく新しい型です。Day 型の変数は、この 7 つの定数のいずれか一つ(または null)だけを保持でき、それ以外は保持できません。コンパイラは Day d = "WEDNESDAY"; や Day d = 2; を拒否します。この保証こそが目的のすべてです。
各定数(Day.MONDAY など)は、一度だけ作られる単一の共有インスタンスです。MONDAY は常に一つしか存在しないため、enum は equals ではなく == で比較します。同じオブジェクトだからです。
enum での switch
enum は switch と自然に組み合わさります。switch の中では、各定数を型名で修飾する必要すらありません。
switch の中では case Day.SATURDAY ではなく case SATURDAY と書きます。Java が型を推論します。文字列や int での switch に対する大きな利点は、後で新しい定数を追加したときに、その処理を忘れているすべての switch を IDE や網羅性チェックが指摘できることです。
すべての定数をループする
すべての enum は、定数を宣言順に返す静的な values() メソッドと、各定数の 0 始まりの位置を返す ordinal() を自動的に得ます。
name() は定数の識別子を String として返し、values() はメニューの構築や選択肢の反復処理に最適です。一つ注意があります。ordinal() を永続的な場所に保存してはいけません。後で誰かが定数を並べ替えると番号がずれ、保存したデータが壊れてしまいます。ordinal() は安定した ID ではなく、実装の詳細として扱いましょう。
enum は本物のクラス: フィールドとメソッド
ここが、Java の enum が他言語の名前付き整数の enum を超えるところです。各定数はコンストラクタを通じて渡されるデータを持つことができ、enum はメソッドを持てます。まず定数を列挙し、それぞれにコンストラクタ引数を与え、その後セミコロンに続けてフィールド、コンストラクタ、メソッドを宣言します。
構造に注目してください。定数が先に来てセミコロンで終わり、その後にクラスの残りが続きます。コンストラクタは暗黙的に private です。許可される唯一のインスタンスは先頭で宣言されたものだけなので、new Planet(...) と書くことは決してできません。final フィールドは各定数を不変にし、これは共有シングルトンにまさに求められることです。
文字列からの変換と逆変換
文字列を対応する定数に変換するには、自動生成される valueOf を使います。
valueOf は大文字・小文字を含めて定数名を完全に一致させ、一致がなければ IllegalArgumentException をスローします。これがよくある落とし穴です。"shipped" は SHIPPED に一致しません。ユーザー入力や外部データを解析するときは、まず大文字・小文字を正規化し(input.toUpperCase())、呼び出しを try/catch で囲みましょう。さもないと最初の不正な値でクラッシュします。
定数ごとの振る舞い
ときには、各定数が異なるデータを持つだけでなく、異なる振る舞いをする必要があります。enum に抽象メソッドを与え、各定数が小さな本体で独自の実装を提供させることができます。
これは「定数固有メソッド」パターンと呼ばれることがあります。定数に対する巨大な switch を、各定数に直接結び付けた振る舞いで置き換えます。新しい演算を追加すると、コンパイラがその apply を定義するよう強制するので、どのケースも忘れることがありません。
enum を使うべきとき
ある値が本質的に、小さく固定された、コンパイル時に既知の集合のいずれかである場合は、常に enum を使いましょう。
- ワークフローの状態(
PENDING、SHIPPED、DELIVERED)。 - カテゴリ、モード、種類(
READ、WRITE、EXECUTE)。 public static final int定数のかたまりを定義したくなる場面のすべて。enum は同じ名前付きの値に加えて、型安全性、読みやすいtoString、そしてswitchのサポートを無料で与えてくれます。
開いた集合や実行時に決まる集合(ユーザー名、データベースから取得した国の一覧)には enum を使わないでください。enum はコンパイル時に焼き付けられます。プログラムの実行中に集合が変化するなら、代わりに通常のコレクションが必要です。
次へ: ジェネリクス
ここまでで values() が Planet[] を返すのを見ましたし、以前のページでは List<Shape> の使用も見ました。この <...> という構文がジェネリクスであり、完全な型安全性を保ちながら多くの型に対して動作する単一のクラスやメソッドを書くための Java の方法です。次は、List<String> や Map<K, V> が実際にどう動くのか、そして独自のジェネリック型をどう書くのかを解き明かします。
よくある質問
Java の enum とは何ですか?
enum は、その値が固定された名前付き定数の集合のいずれか一つにしかなれない特別な型です。たとえば Day.MONDAY や Status.ACTIVE などです。各定数は enum のあらかじめ作られた唯一のインスタンスです。enum は型安全性を与えてくれます。Day を受け取るメソッドは必ず実在する曜日だけを受け取り、タイプミスした文字列や範囲外の int を受け取ることは決してありません。
Java の enum はフィールドやメソッドを持てますか?
はい。enum は完全なクラスです。各定数にコンストラクタ引数を渡し、それを private final フィールドに保持し、それらを使うメソッドを追加できます。たとえば MERCURY(3.3e23, 2.4e6) は質量と半径を enum のコンストラクタに渡し、surfaceGravity() メソッドはそれらのフィールドから計算できます。
Java の enum における values() と valueOf() の違いは何ですか?
values() は enum のすべての定数を宣言順に並べた配列を返します。すべての選択肢をループするのに便利です。valueOf("NAME") はその逆で、名前が文字列と完全に一致する定数を探し、一致するものがなければ IllegalArgumentException をスローします。どちらもコンパイラが自動的に生成します。