1. 概要

ほとんどすべてのアプリケーションで列挙が見られます。 これには、 DRAFTPROCESSING、などの注文ステータスコードや、400、404、500、501などのWebエラーコードが含まれます。 ドメインに列挙データが表示されると、アプリケーションにEnumが表示されます。 着信リクエストでデータを使用して、その列挙型を見つけることができます。 たとえば、Webエラー400BAD_REQUESTにマップできます。

そのため、列挙型を基準で検索するロジックが必要です。 これは、その名前または値である可能性があります。 または、任意の整数コードにすることもできます。

このチュートリアルでは、基準によって列挙型を検索する方法を学習します。 さらに、見つかった列挙型を返すさまざまな方法についても説明します。

2. 名前による列挙型の検索

まず、列挙型は特別なデータ型であることがわかっています。 これにより、変数を事前定義された定数のセットにすることができます。 方向の列挙型を定義しましょう:

public enum Direction {
    EAST, WEST, SOUTH, NORTH;
}

列挙値の名前は定数です。 したがって、たとえば、Direction.EASTの名前はEASTです。 これで、名前で方向を検索できます。 大文字と小文字を区別しない検索を実装することをお勧めします。 その結果、 East east 、およびEASTはすべてDirection.EASTにマップされます。 次のメソッドをDirection列挙型に追加しましょう。

public static Direction findByName(String name) {
    Direction result = null;
    for (Direction direction : values()) {
        if (direction.name().equalsIgnoreCase(name)) {
            result = direction;
            break;
        }
    }
    return result;
}

この実装では、指定された名前の列挙型が見つからない場合、nullを返します。 見つからないシナリオをどのように扱うかは私たち次第です。 1つのオプションは、デフォルトの列挙値を返すことができることです。 逆に、例外をスローすることもできます。 列挙型を検索する例はまもなく表示されます。 次に、検索ロジックをテストしてみましょう。 まず、前向きなシナリオ:

@Test
public void givenWeekdays_whenValidDirectionNameProvided_directionIsFound() {
    Direction result = Direction.findByName("EAST");
    assertThat(result).isEqualTo(Direction.EAST);
}

この記事の最後に、完全なコード実装へのリンクを提供しますが、ここでは、コードスニペットに焦点を当てます。 ここでは、「EAST」という名前の方向を検索し、Direction.EASTを取得する予定です。 前述のように、検索では大文字と小文字が区別されないことがわかっているため、「east」または「East」という名前でも同じ結果が得られるはずです。 私たちの期待を検証しましょう:

@Test
public void givenWeekdays_whenValidDirectionNameLowerCaseProvided_directionIsFound() {
    Direction result = Direction.findByName("east");
    assertThat(result).isEqualTo(Direction.EAST);
}

検索メソッドが「East」という名前に対して同じ結果を返すかどうかを検証するために、もう1つのテストを追加することもできます。 次のテストは、「East」という名前で同じ結果が得られることを示しています。

@Test public void givenWeekdays_whenValidDirectionNameLowerCaseProvided_directionIsFound() { 
    Direction result = Direction.findByName("East"); 
    assertThat(result).isEqualTo(Direction.EAST); 
}

3. 値による列挙型の検索

次に、1週間の日数の列挙型を定義しましょう。 今回は、名前と一緒に値を指定しましょう。 実際、列挙型内で任意のデータメンバーを定義し、それをアプリケーションロジックに使用できます。 Weekday列挙型のコードは次のとおりです。

public Weekday {
    MONDAY("Monday"),
    TUESDAY("Tuesday"),
    // ...
    SUNDAY("Sunday"),
    ;
    private final String value;

    Weekday(String value) {
        this.value = value;
    }
}

次に、値による検索を実装しましょう。 したがって、「月曜日」の場合は、Weekday.MONDAYを取得する必要があります。 次のメソッドを列挙型に追加しましょう。

public static Weekday findByValue(String value) {
    Weekday result = null;
    for (Weekday day : values()) {
        if (day.getValue().equalsIgnoreCase(value)) {
            result = day;
            break;
        }
    }
    return result;
}

ここでは、列挙型の定数を繰り返し処理してから、入力された値を列挙型の値メンバーと比較しています。 前述のように、値の大文字と小文字は無視します。 これでテストできます。

@Test
public void givenWeekdays_whenValidWeekdayValueProvided_weekdayIsFound() {
    Weekday result = Weekday.findByValue("Monday");
    assertThat(result).isEqualTo(Weekday.MONDAY);
}

有効な値を指定しない場合は、nullが返されます。 これを検証しましょう:

@Test
public void givenWeekdays_whenInvalidWeekdayValueProvided_nullIsReturned() {
    Weekday result = Weekday.findByValue("mon");
    assertThat(result).isNull();
}

検索は必ずしも文字列値で行う必要はありません。 最初に入力を文字列に変換してから検索メソッドに渡す必要があるため、これは非常に不便です。 次に、整数値などの文字列以外の値で検索する方法を見てみましょう。

4. 整数値による列挙型の検索

Monthという新しい列挙型を定義しましょう。 Month列挙型のコードは次のとおりです。

public enum Month {
    JANUARY("January", 1),
    FEBRUARY("February", 2),
    // ...
    DECEMBER("December", 12),
    ;

    private final String value;
    private final int code;

    Month(String value, int code) {
        this.value = value;
        this.code = code;
    }
}

月の列挙型には、値とコードの2つのメンバーがあり、コードは整数値であることがわかります。 コードで月を検索するロジックを実装しましょう。

public static Optional<Month> findByCode(int code) {
    return Arrays.stream(values()).filter(month -> month.getCode() == code).findFirst();
}

Java 8の機能を使用して検索を実装する別の方法を示したため、この検索は以前の検索とは少し異なります。 ここでは、列挙型自体を返す代わりに、列挙型のオプション値を返します。 同様に、 null の代わりに、空のオプションを返します。 したがって、コード1を月で検索すると、Month.JANUARYが得られます。 これをテストで検証してみましょう。

@Test
public void givenMonths_whenValidMonthCodeProvided_optionalMonthIsReturned() {
    Optional<Month> result = Month.findByCode(1);
    assertThat(result).isEqualTo(Optional.of(Month.JANUARY));
}

無効なコード値の場合、空のオプションを取得する必要があります。 これもテストで検証しましょう。

@Test
public void givenMonths_whenInvalidMonthCodeProvided_optionalEmptyIsReturned() {
    Optional<Month> result = Month.findByCode(0);
    assertThat(result).isEmpty();
}

より厳密な検索を実装したい場合があります。 そのため、無効な入力を許容せず、これを示すために例外をスローします。

5. 検索メソッドからスローされる例外

null または空のオプション値を返す代わりに、例外をスローしたい場合があります。 どの例外をスローするかは、システムのニーズに完全に依存します。 列挙型が見つからない場合は、IllegalArgumentExceptionをスローすることを選択します。 検索メソッドのコードは次のとおりです。

public static Month findByValue(String value) {
    return Arrays.stream(values()).filter(month -> month.getValue().equalsIgnoreCase(value)).findFirst().orElseThrow(IllegalArgumentException::new);
}

例外をスローしている間、Java8スタイルを使用していることが再びわかります。 テストで検証してみましょう。

@Test
public void givenMonths_whenInvalidMonthValueProvided_illegalArgExIsThrown() {
    assertThatIllegalArgumentException().isThrownBy(() -> Month.findByValue("Jan"));
}

この記事で説明する検索方法はそれを行う唯一の方法ではありませんが、最も一般的なオプションを表しています。 システムのニーズに合わせて、これらの実装を微調整することもできます。

6. 結論

この記事では、列挙型を検索するさまざまな方法を学びました。 また、結果を返すさまざまな方法についても説明しました。 最後に、これらの実装を堅実な単体テストで裏付けました。

いつものように、この記事に関連するコードは、GitHubから入手できます。