http://openjdk.java.net/jeps/325 をテキトーに訳した。
JEP 325: Switch Expressions
Author Brian Goetz Owner Jan Lahoda Created 2017/12/04 08:56 Updated 2018/01/29 16:23 Type Feature Status Candidate Component specification/language Scope SE Discussion amber dash dev at openjdk dot java dot net Effort M Duration M Priority 3 Reviewed by Alex Buckley Issue 8192963
Summary
switch
を文でも式としても使えるよう拡張し、また、nullの扱い方を改良します。この変更は今までの書き方を単純化するもので、switch
のパターンマッチング(JEP 305)に備える目的もあります。
Motivation
我々はJava言語にパターンマッチング(JEP 305)を導入するための拡張の準備をしていますが、既存のswitch
の微妙ないくつかの点が障害となっており、これはユーザを長年イライラさせ続けているものでもあります。これにはnullの処理(switch
は引数がnull
だとNullPointerException
をスロー)と、swtich
が文でしか使えない、があります。場合にもよりますが、式として複数条件分岐*1を表現する方が自然な場合があります。
既存のswitch
文の多くの使われ方は本質的にはswitch式のシミュレーションで、各arm*2は共用のターゲット変数に代入するか値を返すかのどちらかをします。
int numLetters; switch (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = 8; break; case WEDNESDAY: numLetters = 9; break; default: throw new IllegalStateException("Wat: " + day); };
文としてこれを表現するのは、似たようなことの繰り返しになるので、回りくどく間違いやすいです。上記コードの意図は日にちに対応するnumLetters
の値を計算をする、というものです。これは、より直感的で、明瞭・安全に書けるのが望ましいです。
int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; };
switch
式への拡張は更に別の要求へと繋がっています。単純なORパターン形式(上の例で見たもの)、フローアナリシスの拡張(式は常に値を計算するか即時終了する*3)、switch
式の各case
で値の算出ではなく例外をスロー可能にする、などです。
Description
式として使えるようにswitch
文を拡張します。よくある例としては、switch
式は以下のようになります。
T result = switch (arg) { case L1 -> e1; case L2 -> e2; default -> e3; }
switch
式は複合的な式(poly expression)になります。ターゲット型が既知であれば、その型は各armにプッシュダウンします。既知であればswitch
式の型はそのターゲット型となり、そうでない場合、各case
の型との組み合わせでスタンドアローンの型が算出されます*4。
switch
文では、break
がswitchの実行を停止させます。switch
式で使うために、break
が引数を取れるよう拡張し、その値はswitch
式の値になります。
switch式のcaseはbreak
を介して式で値を算出しつつ文の実行も出来ます。その場合switch文のように複数の文を書きます。
int result = switch (s) { case "Foo": break 1; case "Bar": break 2; default: System.out.println("Neither Foo nor Bar, hmmm..."); break 3; }
ただし、上記のような例は稀で、文は無く単一の式を評価する複数のcase、を想定しています。また、シンタックスシュガーを提供するので、その場合はswitch式は以下のように定義します。
case LABEL -> expression;
元はこうです。
case LABEL: break expression;
よって、上記例は簡潔に書けます。
int result = switch (s) { case "Foo" -> 1; case "Bar" -> 2; default: System.out.println("Neither Foo nor Bar, hmmm..."); break 3; }
また、switch
式では以下のようにも書けます。
case LABEL -> throw e;
元はこうです。
case LABEL: throw e;
よって、以下のようになります。
int result = switch (s) { case "Foo" -> 1; case "Bar" -> 2; default -> throw new IllegalStateException(s); }
これらの書き方は必要に応じて使い分けられるので、上記のswitch式は以下のようにも書けます。
int result = switch (s) { case "Foo" -> 1; case "Bar" -> 2; default: System.out.println("Neither Foo nor Bar, hmmm..."); break 3; }
break
の2つの形式(値有・無)はメソッドのreturn
の2つの形式と似ています。両者ともメソッド実行を即時停止し、非void
メソッドでは加えて、メソッドの呼び出し元に値を返す必要があります。(break expression-value
とbreak label
のあいまいさは比較的簡単に処理可能です)
switch
式のcaseは網羅的である必要があり、とりうる入力に対しarmのうち一つだけが評価される必要があります。これは基本的にはdefault
句が必要という意味ですが、enum
のswitch式で取りうる値をすべて網羅する場合(and eventually, switch expressions over sealed types)、default句はコンパイラが挿入して、コンパイラはenum
定義がコンパイル時と実行時で変わったことを示します。(これは現状開発者が手でやっていますが、コンパイラによる挿入は面倒を省いてくれて、エラーメッセージは手で書いたよりも記述的となります)
更にswitch
の改良を推し進めて、switch
の式と文をある程度同じにします。case
句で一つしか書けないのをカンマ区切りのリストで書けるようにします。
switch (day) { case MONDAY, FRIDAY, SUNDAY: numLetters = 6; break; ... };
引数が参照型(現状ではプリミティブのラッパー・文字列・enumのみ)のswitch
では、case
句は明示的にnull
を指定できます。
String formatted = switch (s) { case null -> "(null)"; case "" -> "(empty)"; default -> s; }
もしswitch
にcase null
句が無い場合、最初のcase
句は以下であると解釈されます。
case null: throw new NullPointerException();
この挙動はswitch
で参照型を用いる際の現行の振る舞いと一致します。
なおもし余裕があれば、switchで現行では使えないプリミティブ型、float
, double
, long
など、を使えるようにする拡張もするかもしれません。
Dependencies
Pattern Matching (JEP 305)はこのJEPに依存します。