11 switch式および文
switch
キーワードは、文または式として使用できます。あらゆる式と同様、switch
式は1つの値に評価され、文での使用が可能です。switch式では、フォール・スルーを防ぐためのbreak
文が不要になるcase L ->
ラベルを使用できます。switch式の値の指定には、yield
文を使用します。
switch
式の設計に関する背景情報は、JEP 361を参照してください。
矢印case
曜日の文字数を出力する次のswitch
文について検討してみます。
public enum Day { SUNDAY, MONDAY, TUESDAY,
WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; }
// ...
int numLetters = 0;
Day day = Day.WEDNESDAY;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
}
System.out.println(numLetters);
曜日名の長さは、変数numLetters
に格納するかわりに値を戻せた方が便利で、これはswitch
式を使用して行うことができます。また、フォール・スルーを防ぐのに、記述が面倒で付け忘れがちなbreak
文が必要がなくなれば、さらに便利です。これは、矢印caseを使用して実行できます。次のswitch
式では、矢印caseを使用して曜日の文字数を出力します:
Day day = Day.WEDNESDAY;
System.out.println(
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
}
);
矢印caseの形式は次のとおりです:
case label_1, label_2, ..., label_n -> expression;|throw-statement;|block
Javaランタイムが矢印の左側にある任意のラベルに一致すると、矢印の右側にあるコードが実行されて、フォール・スルーしません。つまり、switch
式(または文)内の他のコードは実行されません。矢印の右側のコードが式である場合、その式の値はswitch
式の値になります。
矢印caseをswitch
文で使用できます。次の例は最初の例に似ていますが、コロンcaseではなく矢印caseを使用します:
int numLetters = 0;
Day day = Day.WEDNESDAY;
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> numLetters = 6;
case TUESDAY -> numLetters = 7;
case THURSDAY, SATURDAY -> numLetters = 8;
case WEDNESDAY -> numLetters = 9;
};
System.out.println(numLetters);
矢印caseとその右側のコードを合せて、switch
ラベル付きルールと呼びます。
コロンcaseとyield文
コロンcaseとは、case L:
の形式のcase
ラベルです。コロンcaseをswitch
式で使用できます。コロンcaseと右側のコードを合せて、switch
ラベル付き文グループと呼びます:
Day day = Day.WEDNESDAY;
int numLetters = switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
yield 6;
case TUESDAY:
System.out.println(7);
yield 7;
case THURSDAY:
case SATURDAY:
System.out.println(8);
yield 8;
case WEDNESDAY:
System.out.println(9);
yield 9;
};
System.out.println(numLetters);
前の例ではyield
文が使用されています。引数を1つ取りますが、それはコロンcaseがswitch
式で生成する値です。
yield
文により、switch
文とswitch
式を容易に区別できるようになります。switch
文(switch
式ではない)は、break
文のターゲットとすることができます。反対に、switch
式(switch
文ではない)は、yield
文のターゲットとすることができます。
ノート:
矢印caseを使用することをお薦めします。コロンcaseの使用時は、break
文またはyield
文を挿入するのを忘れがちです。これを忘れると、コード内で思いがけないフォール・スルーが発生する場合があります。
矢印caseで、複数の文または式でないコード、あるいはthrow
文を指定するには、それらを1つのブロックに入れます。矢印caseが生成する値をyield
文で指定します:
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> {
System.out.println(6);
yield 6;
}
case TUESDAY -> {
System.out.println(7);
yield 7;
}
case THURSDAY, SATURDAY -> {
System.out.println(8);
yield 8;
}
case WEDNESDAY -> {
System.out.println(9);
yield 9;
}
};
case定数としての修飾されたenum定数
修飾されたenum
定数をswitch
式および文のcase
定数として使用できます。
セレクタ式がenum
型である次のswitch
式について考えてみます。
public enum Standard { SPADE, HEART, DIAMOND, CLUB }
// ...
static void determineSuitStandardDeck(Standard d) {
switch (d) {
case SPADE -> System.out.println("Spades");
case HEART -> System.out.println("Hearts");
case DIAMOND -> System.out.println("Diamonds");
default -> System.out.println("Clubs");
}
}
次の例では、セレクタ式の型は、2つのenum
型によって実装されているインタフェースです。セレクタ式の型はenum
型ではないため、このswitch
式ではガード付きパターンがかわりに使用されます。
sealed interface CardClassification permits Standard, Tarot {}
public enum Standard implements CardClassification
{ SPADE, HEART, DIAMOND, CLUB }
public enum Tarot implements CardClassification
{ SPADE, HEART, DIAMOND, CLUB, TRUMP, EXCUSE }
// ...
static void determineSuit(CardClassification c) {
switch (c) {
case Standard s when s == Standard.SPADE -> System.out.println("Spades");
case Standard s when s == Standard.HEART -> System.out.println("Hearts");
case Standard s when s == Standard.DIAMOND -> System.out.println("Diamonds");
case Standard s -> System.out.println("Clubs");
case Tarot t when t == Tarot.SPADE -> System.out.println("Spades or Piques");
case Tarot t when t == Tarot.HEART -> System.out.println("Hearts or C\u0153ur");
case Tarot t when t == Tarot.DIAMOND -> System.out.println("Diamonds or Carreaux");
case Tarot t when t == Tarot.CLUB -> System.out.println("Clubs or Trefles");
case Tarot t when t == Tarot.TRUMP -> System.out.println("Trumps or Atouts");
case Tarot t -> System.out.println("The Fool or L'Excuse");
}
}
ただし、switch
式および文では修飾されたenum
定数が許可されるため、この例を次のように書き換えることができます。
static void determineSuitQualifiedNames(CardClassification c) {
switch (c) {
case Standard.SPADE -> System.out.println("Spades");
case Standard.HEART -> System.out.println("Hearts");
case Standard.DIAMOND -> System.out.println("Diamonds");
case Standard.CLUB -> System.out.println("Clubs");
case Tarot.SPADE -> System.out.println("Spades or Piques");
case Tarot.HEART -> System.out.println("Hearts or C\u0153ur");
case Tarot.DIAMOND -> System.out.println("Diamonds or Carreaux");
case Tarot.CLUB -> System.out.println("Clubs or Trefles");
case Tarot.TRUMP -> System.out.println("Trumps or Atouts");
case Tarot.EXCUSE -> System.out.println("The Fool or L'Excuse");
}
}
したがって、enum
定数の名前が修飾されていて、その値がセレクタ式の型への代入互換性を持っていれば、セレクタ式の型がenum
型でない場合にenum
定数を使用できます。
switch式および文でのプリミティブ値
プリミティブ型のchar
、byte
、short
およびint
に加えて、switch
式または文のセレクタ式は、long
型、float
型、double
型およびboolean
型と、対応するboxed型にすることができます。
ノート:
これはプレビュー機能です。プレビュー機能は、設計、仕様および実装が完了したが、永続的でない機能です。プレビュー機能は、将来のJava SEリリースで、異なる形式で存在することもあれば、まったく存在しないこともあります。プレビュー機能が含まれているコードをコンパイルして実行するには、追加のコマンド行オプションを指定する必要があります。『Preview Language and VM Features』を参照してください。詳細は、JEP 455: パターン、instanceofおよびswitchでのプリミティブ型(プレビュー)を参照してください。
セレクタ式の型がlong
、float
、double
およびboolean
の場合、caseラベルはセレクタ式または対応するboxed型と同じ型である必要があります。たとえば:
void whichFloat(float v) {
switch (v) {
case 0f ->
System.out.println("Zero");
case float x when x > 0f && x <= 10f ->
System.out.println(x + " is between 1 and 10");
case float x ->
System.out.println(x + " is larger than 10");
}
}
caseラベル0f
を0
に変更すると、次のようなコンパイル時のエラーが発生します:
error: constant label of type int is not compatible with switch selector type float
2つの浮動小数点リテラルを表現的に等価のcase
ラベルとして使用することはできません。(表現の等価性の詳細は、DoubleクラスのJavaDoc APIドキュメントを参照してください。)次の例では、コンパイラ・エラーが生成されます。値0.999999999f
は、1.0f
と表現的に等価です:
void duplicateLabels(float v) {
switch (v) {
case 1.0f -> System.out.println("One");
// error: duplicate case label
case 0.999999999f -> System.out.println("Almost one");
default -> System.out.println("Another float value");
}
}
boolean
switch
式または文には文や式を含めることができるため、boolean
値の切替えは、3項条件演算子(?:
)の代替として便利です。たとえば、次のコードでは、boolean
switch
式を使用して、false
の場合にロギングを実行します:
long startProcessing(OrderStatus.NEW, switch (user.isLoggedIn()) {
case true -> user.id();
case false -> { log("Unrecognized user"); yield -1L; }
});
switchの網羅性
switch
式または文のcaseは網羅的であることが求められるため、考えられるすべての値に一致するcase
ラベルが存在する必要があります。そのため、switch
式または文には通常default
句が必要です。ただし、既知の定数をすべて網羅するenum
switch
式では、この項の冒頭にある曜日の名前の文字数を出力する例のように、コンパイラによって暗黙的なdefault
句が挿入されます。
パターン・ラベルまたはnull
ラベルを使用する場合、switch
文のcaseは網羅的である必要があります。詳細は、「switchによるパターン・マッチング」および「nullケース・ラベル」を参照してください。
次のswitch
式は、網羅的ではなく、コンパイル時エラーが発生します。そのラベルのタイプ・カバレッジは、String
およびInteger
のサブタイプで構成されます。ただし、セレクタ式のタイプObjectは含まれません:
static int coverage(Object obj) {
return switch (obj) { // Error - not exhaustive
case String s -> s.length();
case Integer i -> i;
};
}
ただし、caseラベルdefault
のタイプ・カバレッジはすべてのタイプであるため、次の例がコンパイルされます:
static int coverage(Object obj) {
return switch (obj) {
case String s -> s.length();
case Integer i -> i;
default -> 0;
};
}
したがって、switch
式または文が網羅的であるためには、そのラベルのタイプ・カバレッジにセレクタ式のタイプが含まれている必要があります。
コンパイラは、セレクタ式のタイプがシール・クラスであるかどうかを考慮します。次のswitch
式がコンパイルされます。セレクタ式のタイプであるS
の許可される唯一のサブクラスであるクラスA
、B
およびC
がタイプ・カバレッジであるため、default
ケース・ラベルは必要ありません:
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;
};
}
コンパイラでは、そのセレクタ式のタイプが汎用シール・クラスの場合、switch
式またはswitch文のタイプ・カバレッジを判断することもできます。次の例では、コンパイルが行われます。インタフェースI
に使用できるサブクラスは、クラスA
およびB
のみです。ただし、セレクタ式のタイプがI<Integer>
であるため、網羅的にするには、switch
ブロックのタイプ・カバレッジはクラスB
にする必要があります。
sealed interface I<T> permits A, B {}
final class A<X> implements I<String> {}
final class B<Y> implements I<Y> {}
static int testGenericSealedExhaustive(I<Integer> i) {
return switch (i) {
// Exhaustive as no A case possible!
case B<Integer> bi -> 42;
};
}
switch
式または文のセレクタ式の型を、汎用レコードにすることもできます。常に、switch
式または文は網羅的であることが必要です。次の例ではコンパイルが行われません2つの値(両方が型A
)を含むPair
との一致はありません:
record Pair<T>(T x, T y) {}
class A {}
class B extends A {}
static void notExhaustive(Pair<A> p) {
switch (p) {
// error: the switch statement does not cover all possible input values
case Pair<A>(A a, B b) -> System.out.println("Pair<A>(A a, B b)");
case Pair<A>(B b, A a) -> System.out.println("Pair<A>(B b, A a)");
}
}
次の例では、コンパイルが行われます。インタフェースIはシールされています。型CとDが、可能性のあるすべてのインスタンスに対応します:
record Pair<T>(T x, T y) {}
sealed interface I permits C, D {}
record C(String s) implements I {}
record D(String s) implements I {}
// ...
static void exhaustiveSwitch(Pair<I> p) {
switch (p) {
case Pair<I>(I i, C c) -> System.out.println("C = " + c.s());
case Pair<I>(I i, D d) -> System.out.println("D = " + d.s());
}
}
switch
式または文が完了時に網羅的だが、実行時にはそうでない場合、MatchException
がスローされます。これが発生する可能性があるのは、網羅的なswitch
式または文を含むクラスがコンパイルされるが、switch
式または文の分析に使用されたシール階層がその後で変更され再コンパイルされている場合です。そのような変更には移行互換性がないため、switch
文または式の実行時にMatchExceptionがスローされることにつながります。したがって、switch
式または文を含むクラスを再コンパイルする必要があります。
次のクラスPrintA
およびシールされたインタフェースOnlyAB
について考えてみます:
class PrintA {
public static void main(String[] args) {
System.out.println(switch (OnlyAB.getAValue()) {
case A a -> 1;
case B b -> 2;
});
}
}
sealed interface OnlyAB permits A, B {
static OnlyAB getAValue() {
return new A();
}
}
final class A implements OnlyAB {}
final class B implements OnlyAB {}
クラスPrintA
のswitch
式は網羅的であり、この例はコンパイルされます。PrintA
を実行すると、値1
が出力されます。しかし、OnlyAB
を次のように編集して、PrintA
ではなく、このインタフェースをコンパイルするとします:
sealed interface OnlyAB permits A, B, C {
static OnlyAB getAValue() {
return new A();
}
}
final class A implements OnlyAB {}
final class B implements OnlyAB {}
final class C implements OnlyAB {}
PrintA
を実行すると、MatchExceptionがスローされます:
Exception in thread "main" java.lang.MatchException
at PrintA.main(PrintA.java:3)
完了とswitch式
switch
式は、値で正常に完了するか、例外をスローして突然完了するかのいずれかである必要があります
A.たとえば、次のコードは、switch
ラベルの付いたルールにyield
文がないため、コンパイルされません:
int i = switch (day) {
case MONDAY -> {
System.out.println("Monday");
// error: block doesn't contain a yield statement
}
default -> 1;
};
次の例は、switch
ラベルの付いた文グループにyield
文がないため、コンパイルされません:
i = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY:
yield 0;
default:
System.out.println("Second half of the week");
// error: group doesn't contain a yield statement
};
switch
式は1つの値に評価される(または例外をスローする)必要があるため、次の例のように、break
、yield
、return
またはcontinue
文を使用してswitch
式をジャンプすることはできません:
z:
for (int i = 0; i < MAX_VALUE; ++i) {
int k = switch (e) {
case 0:
yield 1;
case 1:
yield 2;
default:
continue z;
// error: illegal jump through a switch expression
};
// ...
}