Java Tips | コレクション:Enumコード検索

Java Java
スポンサーリンク

Enumコード検索は「外から来たコード値を“正しいEnum”に変換する」技

業務システムでは、DBやAPI、設定ファイルなどから「コード値」が文字列や数値で飛んできます。
"N" なら新規、"P" なら処理中、"D" なら完了——みたいなやつです。

でもアプリケーション内部では、String のまま扱うより Status.NEW のような Enum にしておいた方が、
型安全で読みやすく、間違いも減ります。

そこで必要になるのが「Enumコード検索」——
「コード値から対応する Enum を探す」ユーティリティです。


前提となる Enum設計:code を持つ Enum を用意する

まずは、コード値を持つ Enum を一つ定義します。

public enum Status {
    NEW("N", "新規"),
    IN_PROGRESS("P", "処理中"),
    DONE("D", "完了");

    private final String code;
    private final String label;

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

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

ここでの大事なポイントは、
「外部とやり取りする“コード値”を、Enum のフィールドとして持たせている」ことです。

name()(NEW, IN_PROGRESS, DONE)はあくまで“ソースコード上の識別子”なので、
外部仕様として固定したい値(DBのカラム、APIのパラメータなど)は code に持たせるのが定石です。


一番素朴なコード検索:Stream で values() をなめる

まずは、シンプルに「全要素をなめて一致するものを探す」実装から。

import java.util.Arrays;

public static Status fromCode(String code) {
    if (code == null) {
        return null;
    }
    return Arrays.stream(Status.values())
                 .filter(s -> s.getCode().equals(code))
                 .findFirst()
                 .orElse(null);
}
Java

使い方の例です。

Status s1 = Status.fromCode("N"); // NEW
Status s2 = Status.fromCode("X"); // null(該当なし)
Java

ここで深掘りしたいポイントは三つです。

一つ目は、「Status.values() で全定数を配列として取り出し、Arrays.stream で Stream にしている」ことです。
これで「全候補を順番にチェックする」流れが作れます。

二つ目は、「filtercode が一致するものだけに絞り、findFirst で最初の1件を取っている」ことです。
コード値は一意である前提なので、1件見つかれば十分です。

三つ目は、「見つからなければ null を返している」ことです。
ここは設計の分岐点なので、次でさらに掘ります。


戻り値を Optional にするか、例外にするか、null にするか

コード検索で一番大事なのは、
「該当する Enum が見つからなかったとき、どう振る舞うか」を決めることです。

パターンは大きく三つあります。

Optional を返すパターン

「見つからないのも普通にあり得る」なら、Optional で返すのが素直です。

import java.util.Optional;

public static Optional<Status> findByCode(String code) {
    if (code == null) {
        return Optional.empty();
    }
    return Arrays.stream(Status.values())
                 .filter(s -> s.getCode().equals(code))
                 .findFirst();
}
Java

使い方の例です。

Status s = Status.findByCode("N").orElse(Status.NEW); // デフォルトを決める
Java

ここでのポイントは、「該当なし」を Optional.empty() という“正常な結果”として扱えることです。

例外を投げるパターン

「このコード値は必ず Enum に存在しているべき」という前提なら、
見つからなかったときに例外を投げるのもアリです。

public static Status requireByCode(String code) {
    return Arrays.stream(Status.values())
                 .filter(s -> s.getCode().equals(code))
                 .findFirst()
                 .orElseThrow(() ->
                         new IllegalArgumentException("Unknown code: " + code));
}
Java

ここでのポイントは、「データ不整合やバグを早めに検知したい」場面で使う、ということです。

null を返すパターン

既存コードとの互換性や、どうしても Optional を使えない事情があるなら、
最初の fromCode のように null を返す選択も現実的です。

ただし、その場合は呼び出し側での null チェックを徹底する必要があります。


パフォーマンスと可読性のための Map キャッシュ

Enum の要素数が少ないうちは values() を毎回なめても問題ありませんが、
呼び出し頻度が高かったり、要素数が多くなってきたりすると、
「コード値→Enum」の Map を事前に作っておく方が効率的です。

import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.Arrays;

public enum Status {
    NEW("N", "新規"),
    IN_PROGRESS("P", "処理中"),
    DONE("D", "完了");

    private final String code;
    private final String label;

    private static final Map<String, Status> CODE_MAP =
            Arrays.stream(values())
                  .collect(Collectors.toMap(
                          Status::getCode,
                          Function.identity()
                  ));

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

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

    public static Status fromCode(String code) {
        return CODE_MAP.get(code); // なければ null
    }
}
Java

ここでの重要ポイントは三つです。

一つ目は、「Enum の static 初期化時に、一度だけ code → Enum の Map を作っている」ことです。
以降の検索は Map#get なので O(1) で高速です。

二つ目は、「Collectors.toMap(Status::getCode, Function.identity()) で、“コード値をキー、Enum 自身を値”とする Map を作っている」ことです。
Function.identity() は「そのまま自分自身を返す」関数です。

三つ目は、「コード値が重複していると toMap が例外になる」ことです。
これは逆に、「コード値の一意性を起動時にチェックしてくれる」というメリットにもなります。


汎用化:どの Enum でも使える「コード検索ユーティリティ」

複数の Enum で同じような「code フィールドを持っていて、そこから検索したい」というパターンがあるなら、
インターフェース+ユーティリティで共通化するのもよくあります。

Code を持つ Enum 用インターフェース

public interface CodeEnum {
    String getCode();
}
Java

Status にこれを実装させます。

public enum Status implements CodeEnum {
    NEW("N", "新規"),
    IN_PROGRESS("P", "処理中"),
    DONE("D", "完了");
    // 省略
}
Java

汎用コード検索ユーティリティ

import java.util.Arrays;

public final class CodeEnums {

    private CodeEnums() {}

    public static <E extends Enum<E> & CodeEnum> E fromCode(
            Class<E> enumType,
            String code
    ) {
        if (code == null) {
            return null;
        }
        return Arrays.stream(enumType.getEnumConstants())
                     .filter(e -> code.equals(e.getCode()))
                     .findFirst()
                     .orElse(null);
    }
}
Java

使い方の例です。

Status s = CodeEnums.fromCode(Status.class, "N");
Java

ここでの重要ポイントは二つです。

一つ目は、「<E extends Enum<E> & CodeEnum> という型パラメータで、“Enum かつ CodeEnum を実装している型”に限定している」ことです。
これにより、getCode() が必ず呼べるようになります。

二つ目は、「Enum ごとに同じような fromCode を書かなくて済む」ことです。
共通のルール(null の扱い、該当なしの扱い)を一箇所に集約できます。


まとめ:Enumコード検索で身につけてほしい感覚

Enumコード検索は、
単に「便利メソッドを生やす」話ではなく、
「外部のコード値と内部の Enum を、きちんと結びつける設計」です。

Enum に code フィールドを持たせ、「外部仕様としての値」をそこに集約する。
values()+Stream で素朴に検索するところから始め、必要に応じて Map キャッシュに進化させる。
該当なしの扱い(Optional/例外/null)を、業務ルールとしてはっきり決める。
インターフェースや汎用ユーティリティで、「どの Enum も同じパターンでコード検索できる」形に整える。

あなたのコードのどこかに、
if ("N".equals(code)) ... else if ("P".equals(code)) ... のような分岐が並んでいる箇所があれば、
それを一度「Enum+コード検索ユーティリティ」に置き換えられないか眺めてみてください。

その小さな整理が、
「コード値と Enum を軸に、外部と内部をきれいに接続できるエンジニア」への、
確かな一歩になります。

タイトルとURLをコピーしました