java-recipes

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

En-03: 金融実務での Enum 活用

実務でよく登場する Enum の活用パターンを解説します。 DB に保存するコード値の管理、注文ステータスの状態遷移バリデーション、取引種別の分類など、 システム設計に直結する実践的なパターンです。

いつ使うか

  • 支払方法・注文ステータスなど、DB に保存する区分値を型安全に扱いたいとき
  • 「受付中 → 処理中 → 完了」のような状態遷移のルールをコードで表現したいとき
  • 画面表示名と DB 保存コードを一元管理して、定義がバラバラになるのを防ぎたいとき

状態遷移の設計方針

canTransitionTo() を Enum 内に定義することで、 遷移ルールがコードの一箇所にまとまります。 サービス層やコントローラで毎回 if 文を書く必要がなくなり、遷移ルールの変更も Enum を修正するだけで済みます。

サンプルコード

EnumFinancialSample.java
public class EnumFinancialSample {

    // 支払方法: コード値とラベルを持つ Enum
    enum PaymentMethod {
        CREDIT("01", "クレジットカード"),
        BANK_TRANSFER("02", "銀行振込"),
        E_MONEY("03", "電子マネー"),
        CASH("04", "現金");

        private final String code;   // DB保存用コード
        private final String label;  // 画面表示用ラベル

        PaymentMethod(String code, String label) {
            this.code = code;
            this.label = label;
        }

        public String getCode() { return code; }
        public String getLabel() { return label; }

        // コード値から Enum を逆引き
        public static PaymentMethod fromCode(String code) {
            for (PaymentMethod method : values()) {
                if (method.code.equals(code)) {
                    return method;
                }
            }
            throw new IllegalArgumentException("不明な支払コード: " + code);
        }
    }

    // 注文ステータス: 状態遷移のバリデーション付き
    enum OrderStatus {
        PENDING("受付中"),
        PROCESSING("処理中"),
        COMPLETED("完了"),
        CANCELLED("キャンセル");

        private final String label;

        OrderStatus(String label) {
            this.label = label;
        }

        public String getLabel() { return label; }

        // 次の状態への遷移が有効かチェック
        public boolean canTransitionTo(OrderStatus next) {
            if (this == PENDING) {
                return next == PROCESSING || next == CANCELLED;
            }
            if (this == PROCESSING) {
                return next == COMPLETED || next == CANCELLED;
            }
            return false; // COMPLETED, CANCELLED は終端
        }
    }

    // 取引種別: 金融取引の分類
    enum TransactionType {
        PURCHASE("PUR", "購入", true),
        REFUND("REF", "返金", false),
        TRANSFER("TRF", "振替", true),
        ADJUSTMENT("ADJ", "調整", false);

        private final String code;
        private final String label;
        private final boolean debitSide; // 借方側かどうか

        TransactionType(String code, String label, boolean debitSide) {
            this.code = code;
            this.label = label;
            this.debitSide = debitSide;
        }

        public String getCode() { return code; }
        public String getLabel() { return label; }
        public boolean isDebitSide() { return debitSide; }
    }

    public static void main(String[] args) {
        // コード値から Enum を取得(DB読み込み想定)
        PaymentMethod method = PaymentMethod.fromCode("02");
        System.out.println("支払方法: " + method.getLabel()); // 銀行振込

        // 状態遷移バリデーション
        OrderStatus status = OrderStatus.PENDING;
        System.out.println("現在: " + status.getLabel());

        if (status.canTransitionTo(OrderStatus.PROCESSING)) {
            status = OrderStatus.PROCESSING;
            System.out.println("→ " + status.getLabel());
        }

        if (status.canTransitionTo(OrderStatus.CANCELLED)) {
            status = OrderStatus.CANCELLED;
            System.out.println("→ " + status.getLabel());
        }

        // 取引種別の表示
        System.out.println("\n=== 取引種別一覧 ===");
        for (TransactionType type : TransactionType.values()) {
            System.out.println(
                "code=" + type.getCode()
                + " label=" + type.getLabel()
                + " debit=" + type.isDebitSide()
            );
        }
    }
}

Java 8 では fromCode() の逆引きに for ループを使います。状態遷移の canTransitionTo() に if-else を使うことで、初心者でも読みやすいコードになります。

よくあるミス・注意点

DB コードは String 型の固定値にする

コード値を intordinal() で代用すると、 Enum の定義順を変更したときに過去データとのマッピングが壊れます。"01""PEND" のように 明示的な固定コード値を使いましょう。

fromCode() に存在しないコードを渡すと例外が発生する

DB や API から読み込んだコード値が Enum に存在しない場合、IllegalArgumentException がスローされます。 外部からのデータを受け取る際は try-catch で例外を捕捉するか、 事前に値の検証を行いましょう。

canTransitionTo() の終端状態を必ず false にする

COMPLETED・CANCELLED のような終端ステータスから別のステータスへの遷移を許可してしまうと、 「キャンセルされた注文が再度処理中になる」という不整合が発生します。 終端ステータスは必ず false を返すようにしましょう。

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

要素数が少なければ問題ありませんが、定数が多い・呼び出し頻度が高い場合はstatic フィールドとしてMap<String, PaymentMethod>をキャッシュしておくと O(1) で参照できます。

テストする観点

  • fromCode() に各定数のコード値を渡したとき、対応する Enum が返ること(正常系・全件)
  • fromCode() に存在しないコード値(空文字、"99" など)を渡したとき IllegalArgumentException がスローされること(異常系)
  • PENDING から PROCESSING への遷移が true を返すこと
  • PENDING から COMPLETED への遷移が false を返すこと(中間ステップを飛ばせない)
  • COMPLETED・CANCELLED からの全遷移が false を返すこと(終端状態)
  • PaymentMethodgetCode()getLabel() が期待どおりの値を返すこと
  • 境界値: Enum の先頭・末尾定数に対して fromCode() が正常動作すること

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