概要

業務システムでは支払方法、取引区分、税率といった「コード値とラベルの対応表」が頻繁に登場します。これをデータベースのマスタテーブルに持たせるか、定数クラスに並べるか、Enum に集約するかは設計上の判断ですが、値の追加漏れやラベルの不整合に強いのは Enum です。この記事では、Enum にフィールド(コード値・表示名・フラグ)とコンストラクタを持たせる基本形から、コード値による逆引きファクトリメソッド fromCode の実装、abstract メソッドと switch 式による振る舞いの切り替えまでを扱います。Java 8 では for ループと abstract メソッドで書いていた処理が、Java 17 では Stream と switch 式でどう簡潔になるかを対比しながら、実務で使える設計パターンを整理します。

使いどころ

支払方法(クレジット・振込・電子マネー・現金)の DB コード値と画面表示名を Enum で一元管理し、コード値からの逆引きを fromCode メソッドで行う

税率区分(標準10%・軽減8%)を Enum に持たせ、金額に対する税込計算を apply メソッドとして実装する

返金対応可否などの業務フラグを Enum フィールドに持たせ、Stream の filter で対象区分だけを抽出する

コード例

EnumAdvancedExample.java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class EnumAdvancedExample {

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

        private final int code;
        private final String displayName;
        private final boolean supportsRefund;

        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) {
            return Arrays.stream(values())
                .filter(pm -> pm.code == code)
                .findFirst()
                .orElseThrow(() ->
                    new IllegalArgumentException("不明な支払方法コード: " + code));
        }
    }

    // switch 式で振る舞いを切り替える(Java 14+)
    enum TaxRate {
        STANDARD, REDUCED;

        public double apply(double price) {
            return switch (this) {
                case STANDARD -> price * 1.10;
                case REDUCED  -> price * 1.08;
            };
        }
    }

    public static void main(String[] args) {
        // コード値からの逆引き
        var pm = PaymentMethod.fromCode(2);
        System.out.println("code=2 → " + pm.getDisplayName());

        // 税込計算
        System.out.printf("標準: %.0f 円%n", TaxRate.STANDARD.apply(1000.0));
        System.out.printf("軽減: %.0f 円%n", TaxRate.REDUCED.apply(1000.0));

        // Stream で返金可能な支払方法だけを抽出
        List<PaymentMethod> refundable = Arrays.stream(PaymentMethod.values())
            .filter(PaymentMethod::supportsRefund)
            .collect(Collectors.toList());
        refundable.forEach(p ->
            System.out.println("返金可: " + p.getDisplayName()));
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

fromCode を Arrays.stream + filter + findFirst で宣言的に書ける。振る舞いの切り替えも switch 式で簡潔に記述でき、abstract メソッドが不要になる。

Java 17
// Java 17: Stream による逆引き
public static PaymentMethod fromCode(int code) {
    return Arrays.stream(values())
        .filter(pm -> pm.code == code)
        .findFirst()
        .orElseThrow(() ->
            new IllegalArgumentException(
                "不明な支払方法コード: " + code));
}

Library Comparison

標準 Enum(フィールド + fromCode)コード値・ラベルの対応が固定的で、コンパイル時に全量が確定しているとき。要素の追加はコード変更+再デプロイが必要。動的な区分管理には向かない。
DB マスタテーブル区分値の追加・変更を再デプロイなしで行いたいとき。型安全性がなく、switch の網羅性チェックも効かない。キャッシュ戦略も検討が必要。
MapStruct / ModelMapperEnum とDTO 間の変換が大量にあるとき。ライブラリ依存が増え、マッピング定義の管理コストが発生する。少量なら手書きで十分。

注意点

Enum のコンストラクタは暗黙的に private であり、public/protected を指定するとコンパイルエラーになる。外部からのインスタンス生成は不可

fromCode のような逆引きメソッドで一致しない値が来た場合の挙動を明確にすること。Optional を返すか例外を投げるかは呼び出し元の性質で決める

abstract メソッドを使う場合、全要素にオーバーライドが必須。要素を追加したときに実装漏れがあるとコンパイルエラーになるため安全だが、要素が多いとコード量が膨らむ

Stream で values() を毎回走査する fromCode は要素数が少ない前提の実装。要素が数十を超える場合は static Map で事前にインデックスを作るほうが効率的

FAQ

fromCode は static Map でキャッシュすべきですか。

要素が十数個程度なら values() の線形探索で実用上問題ありません。数十以上あるなら static Map<Integer, PaymentMethod> を static ブロックで構築するほうが効率的です。

Enum にフィールドを後から追加しても互換性は保たれますか。

Java ソースレベルでは問題ありません。ただし name() でシリアライズしている場合、要素名の変更は非互換になります。フィールド追加は安全ですが、要素のリネームは慎重に。

abstract メソッドと switch 式はどちらを選ぶべきですか。

Java 14 以降なら switch 式が簡潔です。abstract メソッドは要素ごとに長いロジックがある場合に向きます。短い分岐なら switch 式、複雑な処理なら abstract が目安です。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます