switchによるパターン・マッチング
switch
式または文は、そのセレクタ式の値に応じて、制御を複数の文または式のいずれかに移します。セレクタ式は、任意の参照型またはプリミティブ型にできます。
また、case
ラベルにはパターンを含めることができます。その結果、switch
式または文は、そのセレクタ式がパターンと一致するかどうかをテストできます。これは、セレクタ式が定数とまったく同じかどうかをテストする場合に比べて、柔軟性および表現力が向上します。
switch
式および文のパターン・マッチングに関する背景情報は、JEP 441を参照してください。
「instanceofによるパターン・マッチング」の項の、特定の形状の周辺の長さを計算する次のコードを見ていきます:
interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
// ...
public static double getPerimeter(Shape s) throws IllegalArgumentException {
if (s instanceof Rectangle r) {
return 2 * r.length() + 2 * r.width();
} else if (s instanceof Circle c) {
return 2 * c.radius() * Math.PI;
} else {
throw new IllegalArgumentException("Unrecognized shape");
}
}
次のように、パターンswitch
式を使用するようにこのコードを書き直すことができます:
public static double getPerimeter(Shape s) throws IllegalArgumentException {
return switch (s) {
case Rectangle r ->
2 * r.length() + 2 * r.width();
case Circle c ->
2 * c.radius() * Math.PI;
default ->
throw new IllegalArgumentException("Unrecognized shape");
};
}
次の例では、switch
式のかわりにswitch
文を使用します:
public static double getPerimeter(Shape s) throws IllegalArgumentException {
switch (s) {
case Rectangle r:
return 2 * r.length() + 2 * r.width();
case Circle c:
return 2 * c.radius() * Math.PI;
default:
throw new IllegalArgumentException("Unrecognized shape");
}
}
when句
ブール式は、when
句を使用してパターン・ラベルの直後に追加できます。これはガード付きパターン・ラベルと呼ばれます。when
句のブール式はガードと呼ばれます。値がガード付きパターン・ラベルと一致するのは、値がパターンと一致し、したがってガードもtrueと評価される場合です。次に例を示します。
static void test(Object obj) {
switch (obj) {
case String s:
if (s.length() == 1) {
System.out.println("Short: " + s);
} else {
System.out.println(s);
}
break;
default:
System.out.println("Not a string");
}
}
ブール式s.length == 1
は、when
句を使用してcase
ラベルの直後に移動できます:
static void test(Object obj) {
switch (obj) {
case String s when s.length() == 1 -> System.out.println("Short: " + s);
case String s -> System.out.println(s);
default -> System.out.println("Not a string");
}
}
最初のパターン・ラベル(ガイド付きパターン・ラベル)が一致するのは、obj
がString
でその長さが1の場合です。2番目のパターン・ラベルが一致するのはobj
が異なる長さのString
である場合です。
ガード付きパターン・ラベルの形式は、p when e
で、pはパターン、e
はブール式です。pで宣言されるパターン変数のスコープには、eが含まれます。
次の例もwhen
句を示しています。switch
式のセレクタ式として、プリミティブ型double
を使用します:
String doubleToRating(double rating) {
return switch(rating) {
case 0d -> "0 stars";
case double d when d > 0d && d < 2.5d
-> d + " is not good";
case double d when d >= 2.5f && d < 5d
-> d + " is better";
case 5d -> "5 stars";
default -> "Invalid rating";
};
}
ノート:
プリミティブ型を含むガードはプレビュー機能です。プレビュー機能は、設計、仕様および実装が完了したが、永続的でない機能です。プレビュー機能は、将来のJava SEリリースで、異なる形式で存在することもあれば、まったく存在しないこともあります。プレビュー機能が含まれているコードをコンパイルして実行するには、追加のコマンド行オプションを指定する必要があります。『Preview Language and VM Features』を参照してください。詳細は、JEP 455: パターン、instanceofおよびswitchでのプリミティブ型(プレビュー)を参照してください。
次の例では、セレクタ式としてlong
を使用します:
void bigNumbers(long v) {
switch (v) {
case long x when x < 1_000_000L ->
System.out.println("Less than a million");
case long x when x < 1_000_000_000L ->
System.out.println("Less than a billion");
case long x when x < 1_000_000_000_000L ->
System.out.println("Less than a trillion");
case long x when x < 1_000_000_000_000_000L ->
System.out.println("Less than a quadrillion");
default -> System.out.println("At least a quadrillion");
}
}
パターン・ラベルの優位性
多くのパターン・ラベルがセレクタ式の値と一致する場合があります。予測しやすくするために、ラベルはswitch
ブロックに記述された順序でテストされます。さらに、先行するパターン・ラベルが必ず先に一致するため、パターン・ラベルがまったく一致しない場合は、コンパイラでエラーが発生します。次の例では、コンパイル時のエラーが発生します:
static void error(Object obj) {
switch(obj) {
case CharSequence cs ->
System.out.println("A sequence of length " + cs.length());
// error: this case label is dominated by a preceding case label
case String s ->
System.out.println("A string: " + s);
default -> { break; }
}
}
最初のパターン・ラベルcase CharSequence cs
は、2番目のパターン・ラベルcase String s
より優位となります。これは、パターンString s
に一致するすべての値がパターンCharSequence cs
にも一致しますが、その逆の方法は一致しないためです。それは、String
がCharSequence
のサブタイプであるためです。
パターン・ラベルは、定数ラベルより優先させることができます。次の例では、コンパイル時にエラーが発生します:
static void error2(Integer value) {
switch(value) {
case Integer i ->
System.out.println("Integer: " + i);
// Compile-time errors for both cases -1 and 1:
// this case label is dominated by a preceding case label
case -1, 1 ->
System.out.println("The number 42");
default -> { break; }
}
}
// ...
enum Color { RED, GREEN, BLUE; }
// ...
static void error3(Color value) {
switch(value) {
case Color c ->
System.out.println("Color: " + c);
// error: this case label is dominated by a preceding case label
case RED ->
System.out.println("The color red");
}
}
ノート:
ガード付きパターン・ラベルは定数ラベルより優位ではありません。たとえば:static void testInteger(Integer value) {
switch(value) {
case Integer i when i > 0 ->
System.out.println("Positive integer");
case 1 ->
System.out.println("Value is 1");
case -1 ->
System.out.println("Value is -1");
case Integer i ->
System.out.println("An integer");
}
}
値1
は、ガード付きパターン・ラベルcase Integer i when i > 0
と定数ラベルcase 1
の両方に一致しますが、ガード付きパターン・ラベルは定数ラベルより優位ではありません。ガード付きパターンは、一般に決定不能であるため、優位性が確認されません。したがって、caseラベルを順序付けして、定数ラベルが最初に表示され、その後にガード付きパターン・ラベルが続き、その後に非ガード付きパターン・ラベルが続くようにする必要があります。
static void testIntegerBetter(Integer value) {
switch(value) {
case 1 ->
System.out.println("Value is 1");
case -1 ->
System.out.println("Value is -1");
case Integer i when i > 0 ->
System.out.println("Positive integer");
case Integer i ->
System.out.println("An integer");
}
}
パターン変数のスコープとswitch
switch
式または文では、case
ラベルで宣言されたパターン変数のスコープに次のものが含まれます。
case
ラベルのwhen
句:static void test(Object obj) { switch (obj) { case Character c when c.charValue() == 7: System.out.println("Ding!"); break; default: break; } } }
パターン変数
c
のスコープには、宣言c
を含むcase
ラベルのwhen
句が含まれます。-
case
ラベルの矢印の右側にある、式、ブロック、またはthrow
文:static void test(Object obj) { switch (obj) { case Character c -> { if (c.charValue() == 7) { System.out.println("Ding!"); } System.out.println("Character, value " + c.charValue()); } case Integer i -> System.out.println("Integer: " + i); default -> { break; } } }
パターン変数
c
のスコープには、case Character c ->
の右側のブロックが含まれます。パターン変数i
のスコープには、case Integer i ->
の右側にあるprintln
文が含まれます。 -
switch
ラベルが付いた文のグループ(case
ラベル):static void test(Object obj) { switch (obj) { case Character c: if (c.charValue() == 7) { System.out.print("Ding "); } if (c.charValue() == 9) { System.out.print("Tab "); } System.out.println("character, value " + c.charValue()); default: // You cannot use the pattern variable c here: break; } }
パターン変数
c
のスコープには、case Character c
文グループ(2つのif
文とそれに続くprintln
文)が含まれます。switch
文でcase Character c
文グループを実行し、default
ラベルを経由してからdefault
文グループを実行できる場合でも、スコープにはdefault
文グループは含まれません。ノート:
パターン変数を宣言するcaseラベルを経由する可能性がある場合は、コンパイル時エラーが発生します。次の例ではコンパイルが行われません:static void test(Object obj) { switch (obj) { case Character c: if (c.charValue() == 7) { System.out.print("Ding "); } if (c.charValue() == 9) { System.out.print("Tab "); } System.out.println("character"); case Integer i: // Compile-time error System.out.println("An integer " + i); default: System.out.println("Neither character nor integer"); } }
このコードが許可され、セレクタ式の値
obj
がCharacter
であった場合、switch
文はcase Character c
文グループを実行してから、case Integer i
ラベルを経由します。ここで、パターン変数i
が初期化されていないことになります。パターン変数を使用できるその他の例は、「パターン変数のスコープとinstanceof」を参照してください。
nullケース・ラベル
null
の場合、NullPointerException
をスローするために使用されるswitch
式およびswitch
文。現在は、柔軟性を高めるために、null
ケース・ラベルを使用できます。
static void test(Object obj) {
switch (obj) {
case null -> System.out.println("null!");
case String s -> System.out.println("String");
default -> System.out.println("Something else");
}
}
この例では、NullPointerException
をスローするのではなく、obj
がnull
の場合にnull!
を出力します。
null
ケース・ラベルをdefault
ケース・ラベル以外と組み合せることはできません。次のコードでは、コンパイラ・エラーが生成されます。
static void testStringOrNull(Object obj) {
switch (obj) {
// error: invalid case label combination
case null, String s -> System.out.println("String: " + s);
default -> System.out.println("Something else");
}
}
一方、次のコードはコンパイルされます。
static void testStringOrNull(Object obj) {
switch (obj) {
case String s -> System.out.println("String: " + s);
case null, default -> System.out.println("null or not a string");
}
}
セレクタ式がnull
と評価されたときに、switch
ブロックにnull
ケース・ラベルが含まれない場合、NullPointerException
が通常どおりにスローされます。次のswitch
文を考えてみます:
String s = null;
switch (s) {
case Object obj -> System.out.println("This doesn't match null");
// No null label; NullPointerException is thrown
// if s is null
}
パターン・ラベルcase Object obj
はString型のオブジェクトと一致しますが、この例ではNullPointerException
がスローされます。セレクタ式はnull
と評価され、switch
式にnull
ケース・ラベルが含まれていません。