Edited at

Java12が出たので、とりあえずswitch式を試してみた


概要

Java12が出ましたね。変更点、新機能は色々あると思いますが、最もわかりやすい変更点であるswitch文(あるいはswitch式)について試してみました。


注意点

新しいswitch文の書き方は、Java12時点ではプレビュー機能という位置付けらしく、コンパイル時と実行時にオプションを付けないといけません。

コンパイル時


javac --enable-preview --release 12 MultiCaseSample.java


実行時


java --enable-preview MultiCaseSample

また、ここに書いた内容は将来変更される可能性があります。


内容


複数case

case節にカンマ区切りで複数の値を指定できます。

public class MultiCaseSample {

public static void main(String[] args) {
var month = Integer.parseInt(args[0]);

switch(month) {
case 1, 2:
System.out.println("冬ですね");
break;
case 3, 4, 5:
System.out.println("春ですよー");
break;
case 6, 7, 8:
System.out.println("夏真っ盛り");
break;
case 9, 10, 11:
System.out.println("俺たちの秋はこれからだ!");
break;
case 12:
System.out.println("年末なので来年からほんきをだす");
break;
default:
System.out.println("そんな月ありません");
}
}
}


アロー構文

「->」という記号の後に文を書きます。コロンとbreakの記述を省略できます。

ブロックを使えば複数行の処理を書けるそうです。(下の例だと一行ですが…)

あと、ブロックの閉じカッコの後にセミコロンは不要なのですね。

public class ArrowSample {

public static void main(String[] args) {
var month = Integer.parseInt(args[0]);

switch(month) {
case 1, 2 -> System.out.println("冬ですね");
case 3, 4, 5 -> System.out.println("春ですよー");
case 6, 7, 8 -> System.out.println("夏真っ盛り");
case 9, 10, 11 -> System.out.println("俺たちの秋はこれからだ!");
case 12 -> System.out.println("年末なので来年からほんきをだす");
default -> {
System.out.println("そんな月ありません");
}
}
}
}


switch式

switch文はswitch式になりました。文と式の違いは、前者は値を返さないのに対し、後者は値を返すという点です。なので、switch式の評価結果をそのまま変数に代入できますね。

この場合は、switchの閉じカッコの後にセミコロンが必要になります。

また、全てのパターンを網羅していないと(この例の場合はdefault節がないと)コンパイルエラーになります。暗黙的にnullが代入されるとかよりは、そっちのほうがいいですね。

アロー構文を使わないか、ブロックを使う書き方だと、返す値をbreakの後に記述するそうです。

public class SwitchExpressionSample {

public static void main(String[] args) {
var month = Integer.parseInt(args[0]);

var message = switch(month) {
case 1, 2 -> "冬ですね";
case 3, 4, 5 -> "春ですよー";
case 6, 7, 8 -> {
break "夏真っ盛り";
}
case 9, 10, 11 -> "俺たちの秋はこれからだ!";
case 12 -> "年末なので来年からほんきをだす";
default -> "そんな月ありません";
};
System.out.println(message);
}
}

どうも、アロー構文と従来の構文を混ぜるとコンパイルエラーになってしまうようです。

なのでアロー構文を使わないパターンは別途書きました。

public class SwitchExpressionBreakSample {

public static void main(String[] args) {
var month = Integer.parseInt(args[0]);

var message = switch(month) {
case 1, 2:
break "冬ですね";
case 3, 4, 5:
break "春ですよー";
case 6, 7, 8:
break "夏真っ盛り";
case 9, 10, 11:
break "俺たちの秋はこれからだ!";
case 12:
break "年末なので来年からほんきをだす";
default:
break "そんな月ありません";
};
System.out.println(message);
}
}


その他、気になったこと


Enumで全ての要素を列挙する場合はdefault不要か?

defaultがなくても、コンパイル時、実行時ともにエラーはなく動作しました。

public class SwitchExpressionEnumSample {

private enum Month {
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER,
}

public static void main(String[] args) {
var month = Month.values()[Integer.parseInt(args[0])];

var message = switch(month) {
case JANUARY, FEBRUARY -> "冬ですね";
case MARCH, APRIL, MAY -> "春ですよー";
case JUNE, JULY, AUGUST -> "夏真っ盛り";
case SEPTEMBER, OCTOBER, NOVEMBER -> "俺たちの秋はこれからだ!";
case DECEMBER -> "年末なので来年からほんきをだす";
};
System.out.println(message);
}
}


そもそも、switchの条件がnullだったらどうなるか

今回のリリースとは関係なく以前からありうるパターンですが、上記のEnumの例だとnullの場合のcaseがなく、それで網羅されていると言えるのか気になりました。

public class SwitchExpressionNullSample {

private enum Month {
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER,
}

public static void main(String[] args) {
Month month = null;

var message = switch(month) {
case JANUARY, FEBRUARY -> "冬ですね";
case MARCH, APRIL, MAY -> "春ですよー";
case JUNE, JULY, AUGUST -> "夏真っ盛り";
case SEPTEMBER, OCTOBER, NOVEMBER -> "俺たちの秋はこれからだ!";
case DECEMBER -> "年末なので来年からほんきをだす";
};
System.out.println(message);
}
}

結果は下記の通りでした。(switch(month)の部分でぬるぽ)

まあ、そうでしょうね。


Exception in thread "main" java.lang.NullPointerException
at SwitchExpressionNullSample.main(SwitchExpressionNullSample.java:11)


(2019/5/21追記)Enumで全ての要素を網羅していない場合はエラーとなるか?

肝心のパターンが漏れていました…

適当にコメントアウトして、どうなるか試してみます。

public class SwitchExpressionEnumSample {

private enum Month {
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER,
}

public static void main(String[] args) {
var month = Month.values()[Integer.parseInt(args[0])];

var message = switch(month) {
case JANUARY, FEBRUARY -> "冬ですね";
case MARCH, APRIL, MAY -> "春ですよー";
case JUNE, JULY, AUGUST -> "夏真っ盛り";
case SEPTEMBER, OCTOBER, NOVEMBER -> "俺たちの秋はこれからだ!";
// case DECEMBER -> "年末なので来年からほんきをだす";
};
System.out.println(message);
}
}

結果は下記の通り。やはりコンパイルエラーとなるようです。漏れによるバグを防いでくれるので助かりますね。

具体的にどの要素が漏れているのかまでは教えてくれないようですが。


SwitchExpressionEnumSample.java:11: エラー: switch式がすべての可能な入力値をカバーしていません
var message = switch(month) {
^


感想

便利だと思いました。(小並)

アロー構文を使えばbreak漏れによるバグを防げますし、switch式の結果をそのまま代入すれば、値のパターン漏れによるバグを防げます。

また、従来はbreakをあえて書かずにfallthroughさせるテクニックがあり、アロー構文を使う場合はそれができなくなりますが、caseの複数指定ができるようになったので特に困らないと思います。

コンパイル時、実行時にオプションを付けるのはけっこう面倒なので、できるだけ早く正式な機能になってほしいですね。


参考リンク