java-recipes

ホーム Enum(列挙型) › En-02

En-02: Enum にメソッド・フィールドを追加

Java の Enum はフィールド・コンストラクタ・メソッドを持てます。 DB のコード値とのマッピングや、各定数に異なる処理を持たせるabstract メソッドなど、 実務でよく使われる設計パターンを解説します。

いつ使うか

  • DB に保存する数値コードと画面表示名を Enum にまとめて管理したいとき
  • 消費税率・送料計算など、定数ごとに異なる計算ロジックを持たせたいとき
  • コード値から Enum に逆引きするファクトリメソッドが必要なとき

サンプルコード

EnumAdvancedSample.java
public class EnumAdvancedSample {

    // フィールドとメソッドを持つ Enum(支払方法)
    enum PaymentMethod {
        CREDIT(1, "クレジットカード", true),
        BANK_TRANSFER(2, "銀行振込", false),
        E_MONEY(3, "電子マネー", true),
        CASH(4, "現金", false);

        private final int code;              // DB 保存用コード
        private final String displayName;    // 画面表示用名称
        private final boolean supportsRefund; // 返金対応可否

        // コンストラクタ(Enum のコンストラクタは常に private)
        PaymentMethod(int code, String displayName, boolean supportsRefund) {
            this.code = code;
            this.displayName = displayName;
            this.supportsRefund = supportsRefund;
        }

        public int getCode() { return code; }
        public String getDisplayName() { return displayName; }
        public boolean supportsRefund() { return supportsRefund; }

        // コード値から Enum に変換するファクトリメソッド
        public static PaymentMethod fromCode(int code) {
            for (PaymentMethod pm : values()) {
                if (pm.code == code) {
                    return pm;
                }
            }
            throw new IllegalArgumentException("不明な支払方法コード: " + code);
        }
    }

    // abstract メソッドで各要素に異なる処理を実装
    enum TaxRate {
        STANDARD {
            @Override
            public double apply(double price) {
                return price * 1.10; // 標準税率 10%
            }
        },
        REDUCED {
            @Override
            public double apply(double price) {
                return price * 1.08; // 軽減税率 8%
            }
        };

        public abstract double apply(double price);
    }

    public static void main(String[] args) {
        System.out.println("=== 支払方法一覧 ===");
        for (PaymentMethod pm : PaymentMethod.values()) {
            System.out.printf("code=%d name=%s refund=%b%n",
                pm.getCode(), pm.getDisplayName(), pm.supportsRefund());
        }

        System.out.println("\n=== コードから変換 ===");
        PaymentMethod pm = PaymentMethod.fromCode(2);
        System.out.println("code=2 → " + pm.getDisplayName()); // 銀行振込

        System.out.println("\n=== 税率計算 ===");
        double price = 1000.0;
        System.out.printf("標準: %.0f 円%n", TaxRate.STANDARD.apply(price));
        System.out.printf("軽減: %.0f 円%n", TaxRate.REDUCED.apply(price));

        System.out.println("\n=== 返金可能な支払方法のみ ===");
        for (PaymentMethod p : PaymentMethod.values()) {
            if (p.supportsRefund()) {
                System.out.println("  " + p.getDisplayName());
            }
        }
    }
}

Enum にコンストラクタ・フィールド・メソッドを追加することで、定数に付随するデータ(DB コード・表示名など)を一元管理できます。abstract メソッドを使うと各定数に異なる処理を持たせることができます。

よくあるミス・注意点

Enum のコンストラクタは必ず private

Enum のコンストラクタには明示的にアクセス修飾子を書かなくても private として扱われます。 外部から new PaymentMethod() を呼ぶことはできません。 Enum の定数はクラスロード時に JVM が一度だけ生成し、以後はシングルトンとして扱われます。

fromCode() のループ検索は要素数が増えると遅くなる

fromCode() はすべての定数をループで走査するため、 定数の数が多い・呼び出し頻度が高い場合はパフォーマンスに影響します。 頻繁に呼び出す場合は static フィールドとしてMap<Integer, PaymentMethod>をキャッシュしておくと O(1) で参照できます。

abstract メソッドを使うと各要素が匿名クラスになる

TaxRate の例のように abstract メソッドを持つ Enum では、各定数が匿名クラスとしてコンパイルされます。 クラスファイルが TaxRate$1.classTaxRate$2.class のように増えます。 Java 14 以降では switch 式を使うほうがシンプルで読みやすいため、abstract メソッドはあまり使われなくなっています。

toString() を @Override すると valueOf() が動かなくなる場合がある

Enum の toString() はデフォルトでname() と同じ値を返します。 表示用に toString() を上書きすること自体は可能ですが、valueOf()toString() ではなくname() を参照するため壊れません。 ただし、ログやデバッグで toString() の結果をvalueOf() に渡すようなコードは意図しない動作をするので注意しましょう。

テストする観点

  • fromCode() に各定数の code 値を渡したとき、対応する Enum が返ること(正常系・全件)
  • fromCode() に存在しないコード値(例: 0、99)を渡したとき IllegalArgumentException がスローされること(異常系・境界値)
  • TaxRate.STANDARD.apply(1000) が 1100.0 を返すこと
  • TaxRate.REDUCED.apply(1000) が 1080.0 を返すこと
  • TaxRate.apply(0) が 0 を返すこと(境界値: 価格 0 円)
  • PaymentMethodgetCode()getDisplayName()supportsRefund() が期待どおりの値を返すこと

GitHub でソースコードを見る →