【Modern Java】Java 14 で正式導入となった Switch 式(JEP 361)

f:id:Naotsugu:20200724174249p:plain

blog1.mammb.com


JEP 361: Switch Expressions (Standard)

Java 12でプレビュー(JEP 361)として導入された、Switch Expressions が Java 14 で正式導入となりました。

以下のように switch を文ではなく、式として扱えるようになります。

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
};

従来型の case L : 形式に加え、Arrow labels と呼ばれる case L -> 形式がサポートされ、マッチング条件はカンマで複数指定できるようになります。 Arrow labels の右辺のアームには、式、ブロック、throwステートメントに限定されます。旧来の switch では case の中で変数定義した場合にスコープが switch 全体に及びましたが、Arrow labels では単一式か明示的なブロックしか定義できないため、この分かりにくさが解消されています。


Java における 当初の switch 文は C や C++ から流用されており、整数型の値に対してしか利用できませんでした。

Java 1.5 より、列挙が使用できるようになり、Java 1.7 より文字列が利用できるようになりました。

そして今回の Java 14 では将来のより柔軟なパターンマッチの布石となる Switch Expressions が導入されました。


フォールスルー

Java における switch文は C や C++ などの言語を踏襲しており、フォールスルー(fall through)をサポートします。 つまり break を書かない場合に次の case にフローが移ります。この制御フローは低レベルの処理には便利な場面がありますが、高レベルのコンテキストにおいてはエラーを埋め込み易いという問題があります。

switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        System.out.println(6);
        break;
    case TUESDAY:
        System.out.println(7);
        break;
    case THURSDAY:
    case SATURDAY:
        System.out.println(8);
        break;
    case WEDNESDAY:
        System.out.println(9);
        break;
}

Arrow labels では複数の条件をカンマ区切りで記載できるため、以下のように視認性の高い記述が可能となります。

switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
    case TUESDAY                -> System.out.println(7);
    case THURSDAY, SATURDAY     -> System.out.println(8);
    case WEDNESDAY              -> System.out.println(9);
}

列挙型の switch 式の場合は、条件の網羅性がチェックされるようになり安全性が向上しました。

例えば前述の例で WEDNESDAY の条件が不足していた場合はコンパイルエラーになります。 文字列や整数型に対する switch 式の場合は default の定義が無い場合にコンパイルエラーになります。


Switch 式

前述の通り、switch が値を返すことが出来るようになりました。

String s = switch (k) {
    case 1 -> "one";
    case 2 -> "two";
    default -> "many";
};

switch 式は複合式であり、型が規定された場合はその型が強制されますが、規定されない場合は各アームのタイプを組み合わせて判断されます。

System.out.println(
    switch (k) {
        case  1 -> 1;
        case  2 -> "two";
        default -> BigInteger.ZERO;
    }
);


値の生成

switch 式のアームにはたいてい単一の式を使いますが、ブロックが必要な場合には yield ステートメントにて値を生成します。

int j = switch (day) {
    case MONDAY  -> 0;
    case TUESDAY -> 1;
    default      -> {
        int k = day.toString().length();
        int result = f(k);
        yield result;
    }
};

yield ステートメントは旧来型の switch ブロックから値を生成する場合にも利用できます。

int result = switch (s) {
    case "Foo": 
        yield 1;
    case "Bar":
        yield 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        yield 0;
};

この場合にも条件の網羅性のチェックが行われます。


まとめ

Java 14 で正式導入となった Switch 式について説明しました。

他の現代的な言語に比べると、パターンマッチ機能はまだまだ貧弱ではありますが、Java 15 で予定される JEP draft: Pattern matching for switch (Preview) への土台となるものであり歓迎するものです。

将来的には以下の記載も可能になりますし、より高度なパターンマッチがも可能になる予定です。

String formatted = 
    switch (obj) {
        case Integer i -> String.format("int %d", i)
        case Byte b    -> String.format("byte %d", b);
        case Long l    -> String.format("long %d", l); 
        case Double d  -> String.format("double %f", d); 
        case String s  -> String.format("String %s", s); 
        default        -> obj.toString();
    };