S-JIS[2020-03-21/2023-09-23] 変更履歴

switch

Javaのswitch文・switch式について。


概要

switch文・switch式は、値に応じて処理を分岐させる構文。
switch式はJava14以降(プレビュー版はJava12Java13)で使用可能。

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

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


switch文

C言語のswitch文とほぼ同じ構文。

switch () {
case null:	// Java21以降[2023-09-23]
	文; …
	break;
case 値1:
	文; …
	break;
case 値2:
	文; …
	break;
…
case 値n:
	文; …
	//breakなし(fall through)
default:
	文; …
	break;
}

switchに使える式は、整数型(byte, char, short, intのいずれか)の値になるもの。
JDK1.5以降では列挙型も可。
JDK1.7以降では文字列も可。
Java21以降ではレコードシールクラスも可。[2023-09-23]
case null:」(Java21以降)が無い場合、式がnullだとNullPointerExceptionになる。[/2023-09-23]

caseには具体的な値(定数)を指定する。
値にはstatic finalな変数(定数)も使用できるが、条件がある。→caseで使える定数
Java21より前では、 「case null:」と書くとコンパイルエラーになる。[2023-09-23]

どのcaseにも一致しない場合の処理はdefaultに書く。
defaultは通常は一番下に書くと思うが、どこにあっても(先頭にあっても)構文上は問題ない。
Java21以降ではcase nullと一緒に「case null, default:」とも書ける。[2023-09-23]

breakを書くとswitch文を抜ける。(「break ラベル」でそのラベルのブロックを抜ける)
breakを書かないと次のcaseへ処理が継続する。(フォールスルー(fall through)と呼ばれる)
一番最後のcase/defaultはbreakを書かなくてもswitch文を抜けるが、バグを避ける為にも常にbreakを書いておく方が良いと思う。(新しいcaseを末尾に追加した場合とか、caseの位置を移動させた場合の為)
逆に意図的にフォールスルーする場合は、breakを書き忘れているわけではないことを明示する為に、コメントで「// fall through」と書いておくのが良いと思う。[2020-09-16]

複数の値に対して同じ処理を行いたい場合は、(fall throughを利用して)caseを並べて「case 値1: case 値2: 処理」のように記述する。
Java14以降は値をカンマ区切りで並べてcase 値1, 値2: 処理」のように記述することも出来る。


ラベル

「switch」の前にラベルを付けて、switchブロック内のbreakでそのラベルを指定できる。(そのラベルのブロックから抜ける)

outer:	switch(n) {
	case 1:
		switch(s) {
		case "a":
			System.out.println("a");
			break outer;
		default:
			break;
		}
		System.out.println(1);
		break;
	default:
		System.out.println(n);
		break;
	}

※switch式の場合はbreakで脱出することは出来ない


ただ、switch文における「break ラベル」は、普通はswitchの外側のループを抜けるときに使うことの方が多いだろう。

loop:	for (int i = 0; i < 10; i++) {
		switch(i) {
		case 5:
			break loop;
		default:
			break;
		}

		System.out.println(i);
	}

switch式

Java14(プレビュー版ではJava12)から、switchで値を返せるようになった。
各caseの最後で(breakの代わりに)yieldを使って値を返す。
もしくはアロー構文を使って値を返す。


yield

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;

yieldの後ろに書くのは式なので、丸括弧を付けることも出来る。

		case MONDAY:    yield(1);

これは一見メソッド呼び出しのように見えるが、returnの時に式を丸括弧で囲めるのと同じ。
まぎらわしいので、このような書き方はしない方が良いだろう。


yieldメソッドを定義してそれを呼び出そうとすると、「switchの外でyieldを使おうとした」というエラーになる。
構文的には、yieldメソッドの呼び出しよりも、switch式のyield構文の方が優先されるようだ。
(yieldはキーワードではないようだ。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;
                ^

網羅性チェック

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; }
                                                         ^

カンマ区切り複数ラベル

Java14以降では、ひとつのcaseに値をカンマ区切りで複数記述することが出来る(multiple comma-separated labels)。
(「case 値: 処理」の形式の場合、breakが無いとフォールスルー(fall through)することを利用し、複数の値に対して同じ処理を行いたい場合は「case 値1: case 値2: 処理」と書いていた)
特にアロー構文「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);
 
nullとdefaultの例
[2023-09-23]
var day = DayOfWeek.SUNDAY;
var value = switch (day) {
	case null, default -> 6;
	case TUESDAY                -> 7;
	case THURSDAY, SATURDAY     -> 8;
	case WEDNESDAY              -> 9;
};
System.out.println(value);
case nullとdefaultラベルをカンマ区切りで一緒に書くことが出来る。(Java21以降)
(「case default, null」という順序は不可)
(defaultラベルはnull以外とは指定不可)

switch内の変数のスコープ

switch内(case部分)でローカル変数を定義すると、その変数のスコープはswitch内全体となる。
つまり、異なるcaseで同名のローカル変数を定義しようと思っても、出来ない。(重複エラーになる)

	switch(s) {
	case "a":
		int n = 1;
		〜
		break;
	default:
		int n = 9; ←変数nが既に定義されているというコンパイルエラー
		〜
		break;
	}

yieldを使うときも同様。

アロー構文の場合はそもそも変数定義を書けない(ひとつの式しか書けない)ので、スコープの話は関係ない。


データ型のマッチング

Java21以降(プレビュー版ではJava17以降)では、caseにデータ型を指定することで、その型にマッチしたcaseが実行される。

	static void printPatternSwitch(Object o) {
		switch (o) {
		case null:
			System.out.println("*null*");
			break;
		case Integer i:
			System.out.printf("int %d%n", i);
			break;
		case Long l:
			System.out.printf("long %d%n", l);
			break;
		case Double d:
			System.out.printf("double %f%n", d);
			break;
		case String s:
			System.out.printf("String %s%n", s);
			break;
		default:
			System.out.println(o);
			break;
		};
	}
	static String formatterPatternSwitch(Object o) {
		return switch (o) {
			case null      -> "*null*";
			case Integer i -> String.format("int %d", i);
			case Long l    -> String.format("long %d", l);
			case Double d  -> String.format("double %f", d);
			case String s  -> String.format("String %s", s);
			default        -> o.toString();
		};
	}

各caseで定義する変数名は、同一switch内で重複しても問題ない。


異なる変数を使っているcaseへのfall throughはコンパイルエラーになる。

		switch (o) {
		case Integer i:
			// fall through
		case String s:
			System.out.println(s);
			break;
		default:
			break;
		}

レコードのパターンマッチング

Java21以降(プレビュー版ではJava19以降)では、レコードをswitchで指定し、レコードのフィールドを個別の変数として取り出すことが出来る。[2023-09-23]

record Record1(int n) {}
record Record2(int n) {}
	static void recordTest(Object o) {
		switch (o) {
		case Record1(int i):
			System.out.println("rec1=" + i);
			break;
		case Record2(var i):
			System.out.println("rec2=" + i);
			break;
		default:
			System.out.println("default");
			break;
		}
	}

caseにレコードのクラス名を書き、その直後を丸括弧で囲んでフィールドから受け取る変数を書く。
データ型にはvarを指定可能。

レコードのパターンマッチングの詳細


ガードパターン

Java21以降(プレビュー版ではJava17以降)では、パターンマッチした変数を使って、caseに値の条件を記述することが出来る。[2023-09-23]

caseの中に「when」を書き、whenより後に条件を記述する。

	static void test(Object o) {
		switch (o) {
		case String s when s.length() == 1:
			System.out.println("1--" + s);
			break;
		case String s:
			System.out.println("n--" + s);
			break;
		default:
			System.out.println("default");
			break;
		}
	}

なお、「case String s」を「case String s when s.length() == 1」より前に持ってくると、コンパイルエラーになる。


whenより後ろのガード部分は丸括弧で囲むことが出来る。

		switch (o) {
		case String s when (s.length() == 1) -> 1;
		〜
		}

ラムダ式も「->」を使うので、コンパイルが曖昧になる場合は丸括弧で囲むということらしい。

なお、whenより前のパターンマッチング部分は丸括弧で囲むことは出来ない(コンパイルエラーになる)。

		switch (o) {
//×		case (String s when s.length() == 2) -> 2;
//×		case (String s) when s.length() == 3 -> 3;
		〜
		}

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