S-JIS[2019-03-31/2020-03-21] 変更履歴

switch(Java12)

Java12のswitch式(プレビュー版)について。


概要

2019/3/20にリリースされたJava12で、プレビュー版としてswitch式が使えるようになった。

今までのswitch文は文(ステートメント)なので、値を返す事は出来なかった。
switch式は、各caseから値を返す事により、switchの結果として値を返す。

switch文とswitch式の判別は、switchから値を受け取るようにコーディングされていたらswitch式、そうでなかったらswitch文として扱われるようだ。
switch式なのにcaseから値を返すようになっていないとコンパイルエラーになるし、
switch文なのにcaseから値を返すようになっているとコンパイルエラーになる。


switch式はJava12ではプレビュー版の機能なので、この機能を使いたい場合はコンパイル時にjavacコマンドに--enable-previewを付ける必要があり、
また、実行時にjavaコマンドに--enable-previewを付ける必要がある。
JShellで試す場合もjshellコマンドに--enable-previewを付ける。
(Java12のJShellにはバグがあり、--enable-previewを付けないと、通常のswitch文も(switch式扱いしようとして)エラーになる)

> javac --enable-preview --release 12 Example.java
> java --enable-preview Example

break 式

caseのbreakの後ろに値(式)を書くことにより、switch式の結果としてその値を返す。

	var day = DayOfWeek.SUNDAY;
	var value = switch (day) {
		case MONDAY:    break 1;
		case TUESDAY:   break 2;
		case WEDNESDAY: break 3;
		case THURSDAY:  break 4;
		case FRIDAY:    break 5;
		case SATURDAY:  break 6;
		default:        break 100 + day.ordinal();
	};
	System.out.println(value);

当然、変数で値を返すことも可能。

		default: int temp = 100 + day.ordinal(); break temp;
		default: int temp = day.ordinal(); break 100 + temp;

ただし、元々breakにはブロックを脱出するラベルを指定する機能がある。
このラベル名と変数名が被っている場合はコンパイルエラーになる。

temp: {
	var value = switch (day) {
〜
		default: int temp = 100 + day.ordinal(); break temp;
	};
	System.out.println(value);
}

Example.java:14: エラー: 'temp'への参照があいまいです
                default: int temp = 100 + day.ordinal(); break temp;
                                                         ^
  ('temp'はラベルと式の両方です)

※「break 100 + temp;」の場合はtempが変数であることが明らかなので、特にエラーにはならない。

switch式からブロックの外へ脱出することは出来ないので、switch式の「break 識別子」は常に変数として扱ってもいいような気がするが、敢えてコンパイルエラーにしているのかもしれない。


breakに値を書くかどうかは、returnに値を書くかどうかと似ている。

メソッドの戻り型がvoidならreturnに値を書かないし、そうでない場合はreturnに値を書く。
値を返さないswitch(switch文)ならbreakに値を書かないし、そうでない場合(switch式)はbreakに値を書く。


アロー構文

breakを使ってswitch式を作る場合は「case 値: break 式;」と書くが、「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());

ブロックから値を返すには(アロー構文でも)breakで値を書く。

		case MONDAY -> { System.out.println(day); break 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:  break 2;
〜
	};

Example.java:8: エラー: switchでcaseの異なる種類が使用されています
                case TUESDAY:  break 2;
                ^

カンマ区切り複数ラベル

case 値: 処理」の形式の場合、breakが無いとフォールスルー(fall through)するので、複数の値に対して同じ処理を行いたい場合は「case 値1: case 値2: 処理」と書いていた。

Java12プレビュー版では、ひとつの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式(break)の例
	var day = DayOfWeek.SUNDAY;
	var value = switch (day) {
		case MONDAY, FRIDAY, SUNDAY:
			break 6;
		case TUESDAY:
			break 7;
		case THURSDAY, SATURDAY:
			break 8;
		case WEDNESDAY:
			break 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 値: break 式;でも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でブロックを脱出することは出来ない。
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 loop; }
//			case WEDNESDAY              -> { continue; }
//			case WEDNESDAY              -> { return; }
		};
		System.out.println(value);
	}

Example.java:11: エラー: breakが、囲んでいるswitch式の外にあります
                        case WEDNESDAY              -> { break loop; }
                                                         ^

プレビュー機能へ戻る / Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま