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

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

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

コンパイル時
<pre>
javac --enable-preview --release 12  MultiCaseSample.java
</pre>
実行時
<pre>
java --enable-preview MultiCaseSample
</pre>

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

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

```java
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の記述を省略できます。
ブロックを使えば複数行の処理を書けるそうです。（下の例だと一行ですが…）
あと、ブロックの閉じカッコの後にセミコロンは不要なのですね。

```java
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の後に記述するそうです。

```java
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);
    }
}
```

どうも、アロー構文と従来の構文を混ぜるとコンパイルエラーになってしまうようです。
なのでアロー構文を使わないパターンは別途書きました。

```java
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がなくても、コンパイル時、実行時ともにエラーはなく動作しました。

```java
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がなく、それで網羅されていると言えるのか気になりました。

```java
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)の部分でぬるぽ）
まあ、そうでしょうね。
<pre>
Exception in thread "main" java.lang.NullPointerException
        at SwitchExpressionNullSample.main(SwitchExpressionNullSample.java:11)
</pre>

### （2019/5/21追記）Enumで全ての要素を網羅していない場合はエラーとなるか？
肝心のパターンが漏れていました…
適当にコメントアウトして、どうなるか試してみます。

```java
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);
    }
}
```

結果は下記の通り。やはりコンパイルエラーとなるようです。漏れによるバグを防いでくれるので助かりますね。
具体的にどの要素が漏れているのかまでは教えてくれないようですが。
<pre>
SwitchExpressionEnumSample.java:11: エラー: switch式がすべての可能な入力値をカバーしていません
        var message = switch(month) {
                      ^
</pre>

### （2019/9/26追記）Java13での変更点
Java13がリリースされましたが、Switch式の機能はプレビューのままです。ただ、文法に若干変更点があったので追記します。
> アロー構文を使わないか、ブロックを使う書き方だと、返す値をbreakの後に記述するそうです。

Java13では`break`ではなく`yield`を使うように変更されました。

```java
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 -> {
                yield "夏真っ盛り";
                // ↑これ
            }
            case 9, 10, 11 -> "俺たちの秋はこれからだ!";
            case 12 -> "年末なので来年からほんきをだす";
            default -> "そんな月ありません";
        };
        System.out.println(message);
    }
}
```

### （2020/3/18追記）Java14リリース
Java14がリリースされ、switch式は正式な機能になりました。仕様はどうやらJava13から変更はなさそうです。

# 感想
便利だと思いました。（小並）
アロー構文を使えばbreak漏れによるバグを防げますし、switch式の結果をそのまま代入すれば、値のパターン漏れによるバグを防げます。
また、従来はbreakをあえて書かずにfallthroughさせるテクニックがあり、アロー構文を使う場合はそれができなくなりますが、caseの複数指定ができるようになったので特に困らないと思います。

# 参考リンク
- https://qiita.com/nowokay/items/0e860819b6ffb1aca90a
- https://openjdk.java.net/jeps/12