概要
業務システムでは支払方法、取引区分、税率といった「コード値とラベルの対応表」が頻繁に登場します。これをデータベースのマスタテーブルに持たせるか、定数クラスに並べるか、Enum に集約するかは設計上の判断ですが、値の追加漏れやラベルの不整合に強いのは Enum です。この記事では、Enum にフィールド(コード値・表示名・フラグ)とコンストラクタを持たせる基本形から、コード値による逆引きファクトリメソッド fromCode の実装、abstract メソッドと switch 式による振る舞いの切り替えまでを扱います。Java 8 では for ループと abstract メソッドで書いていた処理が、Java 17 では Stream と switch 式でどう簡潔になるかを対比しながら、実務で使える設計パターンを整理します。
使いどころ
支払方法(クレジット・振込・電子マネー・現金)の DB コード値と画面表示名を Enum で一元管理し、コード値からの逆引きを fromCode メソッドで行う
税率区分(標準10%・軽減8%)を Enum に持たせ、金額に対する税込計算を apply メソッドとして実装する
返金対応可否などの業務フラグを Enum フィールドに持たせ、Stream の filter で対象区分だけを抽出する
コード例
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()));
}
}Version Coverage
fromCode を Arrays.stream + filter + findFirst で宣言的に書ける。振る舞いの切り替えも switch 式で簡潔に記述でき、abstract メソッドが不要になる。
// 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 のコンストラクタは暗黙的に private であり、public/protected を指定するとコンパイルエラーになる。外部からのインスタンス生成は不可
fromCode のような逆引きメソッドで一致しない値が来た場合の挙動を明確にすること。Optional を返すか例外を投げるかは呼び出し元の性質で決める
abstract メソッドを使う場合、全要素にオーバーライドが必須。要素を追加したときに実装漏れがあるとコンパイルエラーになるため安全だが、要素が多いとコード量が膨らむ
Stream で values() を毎回走査する fromCode は要素数が少ない前提の実装。要素が数十を超える場合は static Map で事前にインデックスを作るほうが効率的
FAQ
要素が十数個程度なら values() の線形探索で実用上問題ありません。数十以上あるなら static Map<Integer, PaymentMethod> を static ブロックで構築するほうが効率的です。
Java ソースレベルでは問題ありません。ただし name() でシリアライズしている場合、要素名の変更は非互換になります。フィールド追加は安全ですが、要素のリネームは慎重に。
Java 14 以降なら switch 式が簡潔です。abstract メソッドは要素ごとに長いロジックがある場合に向きます。短い分岐なら switch 式、複雑な処理なら abstract が目安です。