S-JIS[2021-09-19/2023-09-23] 変更履歴

switchのパターンマッチング(Java17〜20)

Java17〜20のswitchのパターンマッチング(プレビュー版)について。


概要

2021/9/15にリリースされたJava17で、プレビュー版としてswitch文・switch式でパターンマッチングが使えるようになった。

Java18では、網羅性に関する正確性が強化されたらしいが、文法的な変更は無さそう。[2022-03-23]

Java19では、ガードパターンが&&からwhenに変更になった。[2022-09-21]
また、レコードのフィールドの抽出が出来るようになったが、これは別のJEP(レコードパターン)である。[2022-09-25]

switchのパターンマッチングはJava21で正式機能になった。[2023-09-23]


パターンマッチングswitchはJava17〜20ではプレビュー版の機能なので、この機能を使いたい場合はコンパイル時にjavacコマンドに--enable-previewを付ける必要があり、
また、実行時にjavaコマンドに--enable-previewを付ける必要がある。
JShellで試す場合もjshellコマンドに--enable-previewを付ける。

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

データ型のマッチング

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

nullマッチング

caseにnullラベルを指定できる。

	static void printPatternSwitch(Object o) {
		switch (o) {
		case null:
			System.out.println("*null*");
			break;
		〜
		}
	}
	static String formatterPatternSwitch(Object o) {
		return switch (o) {
			case null      -> "*null*";
			〜
		};
	}

なお、「case null」が無い場合にswitchにnullが来ると、NullPointerExceptionが発生する。
これは従来通りの挙動であり、defaultが実行されるわけではない。


nullと他のラベルを混在させることが出来る。

		switch (o) {
		case null:
		case String s:
			System.out.println("s=" + s);
			break;
		〜
		}
		switch (o) {
		case null, String s:
			System.out.println("s=" + s);
			break;
		〜
		}

この場合、変数sはnullになる。


nullとdefaultを混在させることも出来る。

		switch (o) {
		case null:
		default:
			System.out.println("default");
			break;
		}
		switch (o) {
		case null, default:
			System.out.println("default");
			break;
		}

Java20では「case default, null」の並び順はコンパイルエラーになるようになった。[2023-09-23]

		switch (o) {
		case default, null: // Java20ではコンパイルエラー
			System.out.println("default");
			break;
		}

caseにdefault

case nullと一緒にdefaultラベルを指定することが出来る。[/2023-09-23]

		switch(n) {
		case null, default -> "default";
		〜
		}

アロー構文でなくても使えるが、基本的にはアロー構文で使う為のものだと思う。


Java19以前はnull以外でもdefaultラベルを指定できたが、Java20ではコンパイルエラーになるようになった。

		switch(n) {
		case default -> "default"; // Java20ではコンパイルエラー
		〜
		}
		switch(n) {
		case 1, default -> "one"; // Java20ではコンパイルエラー
		〜
		}
		switch(n) {
		case default, 2 -> "two"; // Java20ではコンパイルエラー
		〜
		}
		switch(n) {
		case 1, default, 3 -> "three"; // Java20ではコンパイルエラー
		〜
		}

ガードパターン

パターンマッチした変数を使って、caseに値の条件を記述することが出来る。

Java17〜18では「&&」、Java19では「when」より後に条件を記述する。[/2022-09-21]

	static void test(Object o) {
		switch (o) {
//		case String s && s.length() == 1:   // Java17〜18
		case String s when s.length() == 1: // Java19
			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」より前に持ってくると、コンパイルエラーになる。


caseの値の部分(ガードも含む)全体を括弧で囲むことも出来る。

		switch (o) {
		case (String s && s.length() == 1) -> 1;	// Java17〜18
		case (String s) && s.length() == 2 -> 2;	// Java17〜18
		case String s && (s.length() == 3) -> 3;	// Java17〜18
		〜
		}

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

Java19では、caseの値の全体を括弧で囲むことは出来なくなった。[2023-09-23]

		switch (o) {
//×		case (String s when s.length() == 1) -> 1;	// Java19〜20
		case (String s) when s.length() == 2 -> 2;	// Java19〜20(Java21では不可)
		case String s when (s.length() == 3) -> 3;	// Java19〜20
		〜
		}

シールクラス

シールクラスをswitchで指定することが出来る。

sealed interface S permits A, B, C {}
final class A implements S {}
final class B implements S {}
record C(int i) implements S {} // Implicitly final

	static int testSealedCoverage(S s) {
		return switch (s) {
			case A a -> 1;
			case B b -> 2;
			case C c -> 3;
		};
	}

この際、網羅性チェックが行われる。
(caseでシールクラスの全てのサブクラスが指定されていないとコンパイルエラーになる。もちろんdefaultがあれば問題ない)


レコード

Java19ではレコードをswitchで指定し、レコードのフィールドを個別の変数として取り出すことが出来る。[2022-09-21]

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を指定可能。

レコードのパターンマッチングの詳細 [2022-09-25]


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