S-JIS[2019-09-22/2020-03-21] 変更履歴
Java13のswitch式(プレビュー版)について。
|
Java12でリリースされたプレビュー版switch式は、Java13でもまだプレビュー版。
Java12では値を返すのにbreakを使っていたが、Java13ではyieldになった。
今までのswitch文は文(ステートメント)なので、値を返す事は出来なかった。
switch式は、各caseから値を返す事により、switchの結果として値を返す。
switch文とswitch式の判別は、switchから値を受け取るようにコーディングされていたらswitch式、そうでなかったらswitch文として扱われるようだ。
switch式なのにcaseから値を返すようになっていないとコンパイルエラーになるし、
switch文なのにcaseから値を返すようになっているとコンパイルエラーになる。
switch式はJava13ではプレビュー版の機能なので、この機能を使いたい場合はコンパイル時にjavacコマンドに--enable-previewを付ける必要があり、
また、実行時にjavaコマンドに--enable-previewを付ける必要がある。
JShellで試す場合もjshellコマンドに--enable-previewを付ける。
(Java13のJShellにはバグがあり、--enable-previewを付けないと、通常のswitch文も(switch式扱いしようとして)エラーになる)
> javac --enable-preview --release 13 Example.java > java --enable-preview Example
caseのyieldの後ろに値(式)を書くことにより、switch式の結果としてその値を返す。
var day = DayOfWeek.SUNDAY; var value = switch (day) { case MONDAY: yield 1; case TUESDAY: yield 2; case WEDNESDAY: yield 3; case THURSDAY: yield 4; case FRIDAY: yield 5; case SATURDAY: yield 6; default: yield 100 + day.ordinal(); }; System.out.println(value);
当然、変数で値を返すことも可能。
default: int temp = 100 + day.ordinal(); yield temp;
default: int temp = day.ordinal(); yield 100 + temp;
※Java12では値を返すのにbreakを使っていたので、tempというラベルがあると「break temp」はエラーになっていたが、Java13ではbreak以外になったので「yield temp」がラベルと混同されることは無く、エラーにならない。
yieldの後ろに書くのは式なので、丸括弧を付けることも出来る。
case MONDAY: yield(1);
これは一見メソッド呼び出しのように見えるが、returnの時に式を丸括弧で囲めるのと同じ。
まぎらわしいので、このような書き方はしない方が良いだろう。
yieldメソッドを定義してそれを呼び出そうとすると、「switchの外でyieldを使おうとした」というエラーになる。
構文的には、yieldメソッドの呼び出しよりも、switch式のyield構文の方が優先されるようだ。
(yieldがJavaのキーワードになったのかはよく分からない。SourceVersion.isKeyword("yield")
はfalseだが、プレビュー版だからかもしれない)
public class Example { public static void main(String... args) { yield(123); } private static void yield(int n) { System.out.println("yield method " + n); } }
↓
Example.java:3: エラー: switch式の外側のyield yield(123); ^ (yieldというメソッドを呼び出すには、yieldを受取り側またはタイプ名で修飾します)
yieldがstaticメソッドの場合、クラス名で修飾すれば呼び出せる。
yieldが
インスタンスメソッドの場合、thisを付ければ呼び出せる。
Example.yield(123);
this.yield(123);
yieldを使ってswitch式を作る場合は「case 値: yield 式;
」と書くが、「case 値 -> 式;
」と書くことも出来る。
var day = DayOfWeek.SUNDAY; var value = switch (day) { case MONDAY -> 1; case TUESDAY -> 2; case WEDNESDAY -> 3; case THURSDAY -> 4; case FRIDAY -> 5; case SATURDAY -> 6; default -> 100 + day.ordinal(); }; System.out.println(value);
アロー構文では、式の他にブロックか例外を書くことが出来る。
(ブロックを作らずに例外を直接書けるのは利便性の為らしい)
default -> throw new IllegalStateException(day.toString());
ブロックから値を返すには(アロー構文でも)yieldで値を書く。
case MONDAY -> { System.out.println(day); yield 1; } default -> { System.out.println(day); throw new IllegalStateException(day.toString()); }
アロー構文の場合はcaseがフォールスルーしないので、breakを書かない記法として使うことも出来るかもしれないが、紛らわしいのでこういう使い方はしない方がいいと思う。
var day = DayOfWeek.SUNDAY; switch (day) { case MONDAY -> System.out.println(1); case TUESDAY -> System.out.println(2); case WEDNESDAY -> System.out.println(3); case THURSDAY -> System.out.println(4); case FRIDAY -> System.out.println(5); case SATURDAY -> System.out.println(6); default -> System.out.println(100 + day.ordinal()); };
→複数のcaseに対して同一の処理をしたい場合は、カンマ区切りで複数の値をcaseに指定する
caseの「->」と「:
」は混在できない。
混在しているとコンパイルエラーになる。
var day = DayOfWeek.SUNDAY; var value = switch (day) { case MONDAY -> 1; case TUESDAY: yield 2; 〜 };
↓
Example.java:9: エラー: switchでcaseの異なる種類が使用されています case TUESDAY: yield 2; ^
「case 値: 処理
」の形式の場合、breakが無いとフォールスルー(fall
through)するので、複数の値に対して同じ処理を行いたい場合は「case 値1: case 値2: 処理
」と書いていた。
Java13プレビュー版では、ひとつのcaseに値をカンマ区切りで複数記述することが出来る(multiple comma-separated labels)。
特にアロー構文「case 値 ->
」はフォールスルーしないので、複数の値に対して同じ処理を行う場合はこの記法を使う。
従来の記法 |
var day = DayOfWeek.SUNDAY; 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; }; |
カンマ区切りの例 |
var day = DayOfWeek.SUNDAY; switch (day) { case MONDAY, FRIDAY, SUNDAY: System.out.println(6); break; case TUESDAY: System.out.println(7); break; case THURSDAY, SATURDAY: System.out.println(8); break; case WEDNESDAY: System.out.println(9); break; }; |
switch式(yield)の例 |
var day = DayOfWeek.SUNDAY; var value = switch (day) { case MONDAY, FRIDAY, SUNDAY: yield 6; case TUESDAY: yield 7; case THURSDAY, SATURDAY: yield 8; case WEDNESDAY: yield 9; }; System.out.println(value); |
switch式(アロー構文)の例 |
var day = DayOfWeek.SUNDAY; var value = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; }; System.out.println(value); |
switch式(値を返すswitch)では、「case 値 -> 式;
」でも「case 値:
yield 式;
」でもcaseの網羅性チェックが行われる。
(switch文(値を返さないswitch)の場合は、(従来どおり)網羅性はチェックされない)
列挙型のswitch式の場合、caseに列挙子の漏れがあるとコンパイルエラーになる。
int等のswitch式の場合、defaultが無いとコンパイルエラーになる。
var day = DayOfWeek.SUNDAY; var value = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; // case WEDNESDAY -> 9; }; System.out.println(value);
↓
Example.java:6: エラー: switch式がすべての可能な入力値をカバーしていません var value = switch (day) { ^
switch式(値を返すswitch)の場合、continueやbreak(ラベル指定breakも)でブロックを脱出することは出来ない。
returnも不可。
loop: for (var day : DayOfWeek.values()) { var value = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> { break; } // case WEDNESDAY -> { break loop; } // case WEDNESDAY -> { continue; } // case WEDNESDAY -> { return; } }; System.out.println(value); }
↓
Example.java:11: エラー: switch式の外側でbreakを実行しようとしています case WEDNESDAY -> { break; } ^