- JAVA
Javaのswitch文の使い方|switch式・アロー・yieldまで
Javaのswitch文は、1つの値を複数の候補(case)と比べて、一致したところの処理を実行する構文です。Java 14 以降は、->(アロー)で書く形式と、値を返す「switch式」が使えるようになり、break の書き忘れによる動作のずれを防ぎやすくなりました。
switch には「コロン形式かアロー形式か」「文か式か」の組み合わせで4通りの書き方があります。この記事では、4通りを同じ題材で書き比べ、break を忘れたときのフォールスルー、使える型と使えない型、enum で default を省いたときの網羅性エラー、null を渡したときの例外、yield が必要になる場面を、実行結果とコンパイルエラー文つきで確かめます。基本構文に絞って扱い、型パターンを使う書き方(パターンマッチング)はこの記事では扱いません。
確認環境:Ubuntu 24.04 / OpenJDK 21.0.10(java ファイル名.java で実行)。掲載した実行結果とエラー文はすべてこの環境の出力です。
switch文とswitch式の早見表
| 書き方 | 例 | break | 値を返す | 特徴 |
|---|---|---|---|---|
| コロン形式の switch 文 | case 1: 処理; break; |
必要 | 返さない | 以前からある形。break を省くと次の case へ進む |
| アロー形式の switch 文 | case 1 -> 処理; |
不要 | 返さない | 一致した case だけを実行する |
| コロン形式の switch 式 | case 1: yield 値; |
使わない | yield で返す | 値を返すが、書く量は多め |
| アロー形式の switch 式 | case 1 -> 値; |
不要 | 返す | いちばん短い。新しく書くならこの形が基本 |
| 知りたいこと | 答え |
|---|---|
| アロー形式と switch 式はいつから? | Java 14 から(JEP 361) |
| 使える型は? | byte・short・char・int とそのラッパー型、String、enum |
| 使えない型は? | long・float・double・boolean(Java 21 でコンパイルエラー) |
| 複数の値を1つの case にまとめるには? | case 1, 2, 3 -> のようにカンマで並べる |
| null を渡すと? | NullPointerException。Java 21 以降は case null で受けられる |
| enum で default を省けるのは? | すべての定数を case に書いた switch 式 |
| yield はいつ使う? | switch 式の case を複数行のブロックにして値を返すとき |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Javaのswitch文の使い方|switch式・アロー・yieldまで」
4通りの書き方を同じ題材で比べる
会員ランクの番号(1〜3、それ以外)から表示名を決める、という同じ処理を4通りで書きます。どれも結果は同じで、違うのは書く量と、break や yield が必要かどうかです。
public class FourForms {
public static void main(String[] args) {
for (int rank = 1; rank <= 4; rank++) {
System.out.println(rank + " : " + colonStatement(rank) + " / " + arrowStatement(rank)
+ " / " + colonExpression(rank) + " / " + arrowExpression(rank));
}
}
// 1) コロン形式の switch 文(Java 1.0 から)
static String colonStatement(int rank) {
String label;
switch (rank) {
case 1:
label = "ゴールド";
break;
case 2:
label = "シルバー";
break;
case 3:
label = "ブロンズ";
break;
default:
label = "一般";
break;
}
return label;
}
// 2) アロー形式の switch 文(Java 14 から)
static String arrowStatement(int rank) {
String label;
switch (rank) {
case 1 -> label = "ゴールド";
case 2 -> label = "シルバー";
case 3 -> label = "ブロンズ";
default -> label = "一般";
}
return label;
}
// 3) コロン形式の switch 式(値は yield で返す)
static String colonExpression(int rank) {
return switch (rank) {
case 1:
yield "ゴールド";
case 2:
yield "シルバー";
case 3:
yield "ブロンズ";
default:
yield "一般";
};
}
// 4) アロー形式の switch 式(いちばん短い)
static String arrowExpression(int rank) {
return switch (rank) {
case 1 -> "ゴールド";
case 2 -> "シルバー";
case 3 -> "ブロンズ";
default -> "一般";
};
}
}
実行結果:
1 : ゴールド / ゴールド / ゴールド / ゴールド
2 : シルバー / シルバー / シルバー / シルバー
3 : ブロンズ / ブロンズ / ブロンズ / ブロンズ
4 : 一般 / 一般 / 一般 / 一般
| 比べる点 | 1) コロン・文 | 2) アロー・文 | 3) コロン・式 | 4) アロー・式 |
|---|---|---|---|---|
| メソッド本体の行数 | 16 | 8 | 10 | 6 |
| break | 各 case に必要 | 不要 | 使わない | 不要 |
| 値の受け取り方 | 変数に代入 | 変数に代入 | yield | 矢印の右の値 |
| 末尾のセミコロン | 不要 | 不要 | 必要 | 必要 |
「値を1つ決める」処理なら、4) のアロー形式の switch 式がいちばん短く書けます。 変数 label を先に宣言して各 case で代入する必要がなく、return switch (...) { ... }; のように結果をそのまま使えます。
switch 式は「式」なので、代入文や return 文の一部として書きます。そのため閉じかっこの後にセミコロンが必要です。書き忘れると次のエラーになります。
ESemi.java:7: error: ';' expected
}
^
Java 11でコンパイルするとどうなるか
アロー形式と switch 式は Java 14 で正式に加わりました。上の FourForms.java を Java 11 向けにコンパイルすると、次のように案内されます。
$ javac --release 11 FourForms.java
FourForms.java:33: error: switch rules are not supported in -source 11
case 1 -> label = "ゴールド";
^
(use -source 14 or higher to enable switch rules)
FourForms.java:43: error: switch expressions are not supported in -source 11
return switch (rank) {
^
(use -source 14 or higher to enable switch expressions)
2 errors
Java 11 以前で動かすコードでは、1) のコロン形式の switch 文を使います。
break忘れのフォールスルーを実行して確かめる
コロン形式では、一致した case から下へ順に実行が進み、break に出会うまで止まりません。 これをフォールスルーと呼びます。break を書き忘れた場合、書いた場合、アロー形式の3つを並べて実行します。
public class FallThrough {
public static void main(String[] args) {
int rank = 1;
System.out.println("--- break を書き忘れた場合 ---");
switch (rank) {
case 1:
System.out.println("ゴールド");
case 2:
System.out.println("シルバー");
case 3:
System.out.println("ブロンズ");
default:
System.out.println("一般");
}
System.out.println("--- break を書いた場合 ---");
switch (rank) {
case 1:
System.out.println("ゴールド");
break;
case 2:
System.out.println("シルバー");
break;
case 3:
System.out.println("ブロンズ");
break;
default:
System.out.println("一般");
}
System.out.println("--- アロー形式(break なし) ---");
switch (rank) {
case 1 -> System.out.println("ゴールド");
case 2 -> System.out.println("シルバー");
case 3 -> System.out.println("ブロンズ");
default -> System.out.println("一般");
}
}
}
実行結果:
--- break を書き忘れた場合 ---
ゴールド
シルバー
ブロンズ
一般
--- break を書いた場合 ---
ゴールド
--- アロー形式(break なし) ---
ゴールド
rank は 1 なので「ゴールド」だけを出力したいところですが、break がないと、その下の case 2、case 3、default の処理まで続けて実行されます。 case の行は「ここから実行を始める」という目印で、「ここで終わる」という区切りではないためです。
アロー形式は、一致した case の右側だけを実行します。 JEP 361 でも、アローのラベルではフォールスルーが起きないと説明されています。break を書く必要がないので、書き忘れそのものが起きません。
コンパイラに知らせてもらう
コロン形式を使い続ける場合は、javac の -Xlint:fallthrough を付けると、フォールスルーの可能性がある箇所を警告してくれます。
$ javac -Xlint:fallthrough FallThrough.java
FallThrough.java:9: warning: [fallthrough] possible fall-through into case
case 2:
^
FallThrough.java:11: warning: [fallthrough] possible fall-through into case
case 3:
^
FallThrough.java:13: warning: [fallthrough] possible fall-through into case
default:
^
3 warnings
複数の値を1つのcaseにまとめる
「3・4・5 は春」のように、複数の値で同じ処理をしたいときは、カンマで並べます(Java 14 以降)。以前からの書き方では、case を縦に並べてフォールスルーを利用します。
public class Multi {
public static void main(String[] args) {
for (int month : new int[] {1, 4, 8, 11, 13}) {
System.out.println(month + "月 : " + seasonOld(month) + " / " + seasonNew(month));
}
// default は先頭や途中にも書ける
int n = 9;
switch (n) {
default:
System.out.println("その他");
break;
case 1:
System.out.println("1");
break;
}
}
// 以前からの書き方: case を縦に並べてフォールスルーを利用する
static String seasonOld(int month) {
String s;
switch (month) {
case 3:
case 4:
case 5:
s = "春";
break;
case 6:
case 7:
case 8:
s = "夏";
break;
case 9:
case 10:
case 11:
s = "秋";
break;
case 12:
case 1:
case 2:
s = "冬";
break;
default:
s = "範囲外";
}
return s;
}
// Java 14 以降: カンマで並べる
static String seasonNew(int month) {
return switch (month) {
case 3, 4, 5 -> "春";
case 6, 7, 8 -> "夏";
case 9, 10, 11 -> "秋";
case 12, 1, 2 -> "冬";
default -> "範囲外";
};
}
}
実行結果:
1月 : 冬 / 冬
4月 : 春 / 春
8月 : 夏 / 夏
11月 : 秋 / 秋
13月 : 範囲外 / 範囲外
その他
メソッド本体は seasonOld が 26 行、seasonNew が 7 行で同じ結果になります。フォールスルーは「case を縦に並べて同じ処理にまとめる」使い方では意図どおりの動きなので、Java 11 以前のコードではこの形がよく使われています。
最後の出力は、default を先頭に書いても動くことの確認です。default は「どの case にも一致しなかったとき」に選ばれ、書く位置は自由です。ただし読みやすさのため、最後に書くのが一般的です。
switchで使える型と使えない型
switch のかっこの中(セレクタ式)に書ける型は決まっています。 使える型を1つのファイルで確かめます。
public class TypesOk {
enum Size { S, M, L }
public static void main(String[] args) {
byte b = 1;
short sh = 2;
char c = 'x';
int i = 4;
Byte wb = 5;
Short ws = 6;
Character wc = 'y';
Integer wi = 8;
String str = "s";
Size size = Size.M;
System.out.println("byte : " + switch (b) { case 1 -> "一致"; default -> "その他"; });
System.out.println("short : " + switch (sh) { case 2 -> "一致"; default -> "その他"; });
System.out.println("char : " + switch (c) { case 'x' -> "一致"; default -> "その他"; });
System.out.println("int : " + switch (i) { case 4 -> "一致"; default -> "その他"; });
System.out.println("Byte : " + switch (wb) { case 5 -> "一致"; default -> "その他"; });
System.out.println("Short : " + switch (ws) { case 6 -> "一致"; default -> "その他"; });
System.out.println("Character : " + switch (wc) { case 'y' -> "一致"; default -> "その他"; });
System.out.println("Integer : " + switch (wi) { case 8 -> "一致"; default -> "その他"; });
System.out.println("String : " + switch (str) { case "s" -> "一致"; default -> "その他"; });
System.out.println("enum : " + switch (size) { case M -> "一致"; default -> "その他"; });
}
}
実行結果:
byte : 一致
short : 一致
char : 一致
int : 一致
Byte : 一致
Short : 一致
Character : 一致
Integer : 一致
String : 一致
enum : 一致
| 型 | 使えるか | 補足 |
|---|---|---|
| byte / short / char / int | 使える | case には定数を書く |
| Byte / Short / Character / Integer | 使える | ラッパー型。null のときは NullPointerException |
| String | 使える | 大文字と小文字を区別する |
| enum | 使える | case には定数名を書く |
| long / Long | 使えない | long は selector type long is not allowed |
| float / double | 使えない | selector type double is not allowed |
| boolean | 使えない | selector type boolean is not allowed |
使えない型を渡したときのエラー文は次のとおりです(Java 21)。
public class ELong {
public static void main(String[] args) {
long id = 1L;
switch (id) {
case 1L -> System.out.println("one");
default -> System.out.println("other");
}
}
}
ELong.java:4: error: selector type long is not allowed
switch (id) {
^
1 error
error: compilation failed
double・float・boolean でも、型名の部分が変わるだけで同じ形のエラーになりました。ラッパー型の Long を渡して case 1L と書いた場合は、constant label of type long is not compatible with switch selector type Long というエラーになります。
Java 言語仕様(Java SE 21 版、14.11 節)では、セレクタ式の型は char・byte・short・int または参照型と定められています。参照型のうち、定数の case と組み合わせて使えるのが上の表のラッパー型・String・enum です。
long・double・booleanを分岐したいときの書き方
public class ELongFix {
public static void main(String[] args) {
long id = 2L;
// 直し方1: int の範囲に収まるとわかっているなら Math.toIntExact で int にする
switch (Math.toIntExact(id)) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
default -> System.out.println("other");
}
// 直し方2: 範囲の判定や小数・真偽値は if / else if で書く
double rate = 0.5;
if (rate >= 0.8) {
System.out.println("高い");
} else if (rate >= 0.4) {
System.out.println("ふつう");
} else {
System.out.println("低い");
}
// 真偽値は条件演算子や if で十分
boolean ok = true;
System.out.println(ok ? "yes" : "no");
}
}
実行結果:
two
ふつう
yes
| 型 | 置き換え方 |
|---|---|
| long | 値が int の範囲に収まるなら Math.toIntExact(値) で int にする(範囲を超えると例外で知らせてくれる) |
| double / float | 小数は「以上・未満」の範囲で判定することが多いので if / else if で書く |
| boolean | 2通りしかないので if 文か条件演算子で書く |
文字列とenumで分岐する
String と enum は、switch と組み合わせることが多い型です。
public class StringEnum {
enum Status { TODO, DOING, DONE }
public static void main(String[] args) {
// 文字列(Java 7 から)。equals と同じく大文字小文字を区別する
for (String cmd : new String[] {"start", "START", "stop"}) {
String msg = switch (cmd) {
case "start" -> "開始します";
case "stop" -> "停止します";
default -> "不明なコマンド: " + cmd;
};
System.out.println(msg);
}
// 大文字小文字をそろえてから渡す
System.out.println(switch ("START".toLowerCase()) {
case "start" -> "開始します";
default -> "不明";
});
// enum: case には定数名だけを書く。全定数を書けば default は不要
for (Status s : Status.values()) {
String label = switch (s) {
case TODO -> "未着手";
case DOING -> "作業中";
case DONE -> "完了";
};
System.out.println(s + " → " + label);
}
// char
char grade = 'B';
switch (grade) {
case 'A' -> System.out.println("優");
case 'B' -> System.out.println("良");
default -> System.out.println("可");
}
// ラッパー型 Integer も使える(自動でアンボクシング)
Integer boxed = 2;
switch (boxed) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
default -> System.out.println("other");
}
}
}
実行結果:
開始します
不明なコマンド: START
停止します
開始します
TODO → 未着手
DOING → 作業中
DONE → 完了
良
two
- String の比較は大文字と小文字を区別します。 "START" は
case "start"に一致せず default に進みました。大文字小文字をそろえたいときは、toLowerCase()などで変換してから switch に渡します。 - enum の case には定数名だけを書きます(
case TODO)。Java 21 ではcase Status.TODOのように型名を付けた書き方もコンパイルできましたが、Java 17 向け(--release 17)では「an enum switch case label must be the unqualified name of an enumeration constant」というエラーになりました。どの版でも通る「定数名だけ」の書き方が無難です。 - enum のすべての定数を case に書いた switch 式は、default を省けます。 次の節で詳しく見ます。
enumでdefaultを省いたときの網羅性エラー
switch 式は、必ず何かの値を返す必要があります。そのため、すべての入力に対応する case があること(網羅性)がコンパイル時に確認されます。 enum の定数を1つ書き漏らすと、次のエラーになります。
public class EEnum {
enum Status { TODO, DOING, DONE }
public static void main(String[] args) {
Status s = Status.DOING;
String label = switch (s) {
case TODO -> "未着手";
case DOING -> "作業中";
};
System.out.println(label);
}
}
EEnum.java:6: error: the switch expression does not cover all possible input values
String label = switch (s) {
^
1 error
error: compilation failed
日本語の設定(javac -J-Duser.language=ja)では「switch式がすべての可能な入力値をカバーしていません」と表示されます。DONE の case を足すか、default を足すとコンパイルが通ります。
この確認は、enum に定数を追加したときに役立ちます。 default を書かずに全定数を並べておけば、定数を追加したときに「この switch 式も直す必要がある」とコンパイラが教えてくれます。default を書いていると、追加した定数は default に流れ、気づく機会がなくなります。
| switch 式での書き方 | 定数を追加したとき |
|---|---|
| 全定数を case に書き、default なし | コンパイルエラーで対応漏れに気づける |
| default あり | 追加した定数は default の処理になる |
switch文は網羅していなくてもコンパイルが通る
一方、値を返さない switch 文は、定数を書き漏らしてもコンパイルが通ります。
public class EEnumStmt {
enum Status { TODO, DOING, DONE }
public static void main(String[] args) {
Status s = Status.DONE;
switch (s) {
case TODO -> System.out.println("未着手");
case DOING -> System.out.println("作業中");
}
System.out.println("switch 文は何も実行せずに通過");
}
}
実行結果:
switch 文は何も実行せずに通過
一致する case がないので、何も実行されずに次の行へ進みました。Java 言語仕様でも、以前からある形の switch 文は互換性のために網羅していなくてもよいと定められています。対応漏れをコンパイル時に見つけたいなら、switch 式にして値を受け取る形にするのが確実です。
int や String の switch 式は default が必要
int や String は取りうる値をすべて書き並べられないので、switch 式では default が必要です。省くと enum のときと同じエラーになります。
EInt.java:4: error: the switch expression does not cover all possible input values
String label = switch (rank) {
^
コンパイル後にenumの定数が増えた場合
網羅性の確認はコンパイル時に行われます。では、switch 式をコンパイルした後に、enum だけ定数を足して再コンパイルするとどうなるでしょうか。次の2ファイルで確かめました。
// Status.java(最初の状態)
public enum Status { TODO, DONE }
public class Main {
public static void main(String[] args) {
for (Status s : Status.values()) {
String label = switch (s) {
case TODO -> "未着手";
case DONE -> "完了";
};
System.out.println(s + " → " + label);
}
}
}
両方をコンパイルした後、Status.java だけを public enum Status { TODO, DONE, ARCHIVED } に書き換えて再コンパイルし、Main を実行した結果です。
TODO → 未着手
DONE → 完了
Exception in thread "main" java.lang.MatchException
at Main.main(Main.java:4)
case にない定数が来ると、実行時に MatchException が発生しました(Java 21)。 黙って通過するのではなく例外で知らせてくれるので、原因にたどり着きやすくなっています。通常は関連するファイルをまとめて再コンパイルするため、その時点でコンパイルエラーとして気づけます。
nullを渡したときの動きと対策
switch に null を渡すと、NullPointerException が発生します。 String・enum・ラッパー型のどれでも同じです。Java 21 以降は case null を書いて null を受けられます。
public class NullDemo {
enum Status { TODO, DONE }
public static void main(String[] args) {
String cmd = null;
try {
switch (cmd) {
case "start" -> System.out.println("開始");
default -> System.out.println("default");
}
} catch (NullPointerException e) {
System.out.println("String に null : " + e.getClass().getName());
}
Status st = null;
try {
switch (st) {
case TODO -> System.out.println("未着手");
case DONE -> System.out.println("完了");
}
} catch (NullPointerException e) {
System.out.println("enum に null : " + e.getClass().getName());
}
Integer boxed = null;
try {
switch (boxed) {
case 1 -> System.out.println("one");
default -> System.out.println("default");
}
} catch (NullPointerException e) {
System.out.println("Integer に null: " + e.getClass().getName());
}
// 対策1: 先に null を確認する
if (cmd == null) {
System.out.println("対策1: コマンドが指定されていません");
}
// 対策2: case null(Java 21 から)
String result = switch (cmd) {
case null -> "対策2: null でした";
case "start" -> "開始";
default -> "default";
};
System.out.println(result);
// case null, default とまとめて書くこともできる
String result2 = switch (cmd) {
case "start" -> "開始";
case null, default -> "対策3: null か、その他";
};
System.out.println(result2);
}
}
実行結果:
String に null : java.lang.NullPointerException
enum に null : java.lang.NullPointerException
Integer に null: java.lang.NullPointerException
対策1: コマンドが指定されていません
対策2: null でした
対策3: null か、その他
default は null を受けません。 最初の switch には default がありますが、null を渡すと default には進まず例外になっています。Java 言語仕様(14.11.3 節)でも、一致するラベルがなくセレクタ式の値が null の場合は NullPointerException が投げられると定められています。
| 対策 | 書き方 | 使える版 |
|---|---|---|
| 先に null を確認する | if (cmd == null) { ... } |
すべての版 |
| case null を書く | case null -> ... |
Java 21 以降 |
| null と default をまとめる | case null, default -> ... |
Java 21 以降 |
case null を含むコードを Java 17 向けにコンパイルすると、次のように案内されます。
$ javac --release 17 NullDemo.java
NullDemo.java:51: error: patterns in switch statements are not supported in -source 17
case null, default -> "対策3: null か、その他";
^
(use -source 21 or higher to enable patterns in switch statements)
1 error
Java 17 以前で動かすコードでは、switch の前に null を確認する形にします。
yieldが必要になる場面
yield は、switch 式の中で「この case の値はこれ」と返すための文です。 必要になるのは次の2つの場面です。
| 場面 | 書き方 |
|---|---|
アロー形式で、右側を複数行のブロック { } にしたとき |
ブロックの最後に yield 値; |
| コロン形式の switch 式 | 各 case で yield 値; |
アローの右側が式1つなら、yield は書きません。ブロックにして途中で変数を使いたいときに yield が登場します。
public class Yield {
public static void main(String[] args) {
for (int qty : new int[] {0, 5, 120}) {
// アローの右側を { } のブロックにしたら、値は yield で返す
String msg = switch (qty) {
case 0 -> "在庫なし";
default -> {
String level = qty >= 100 ? "十分" : "残りわずか";
yield "在庫 " + qty + " 個(" + level + ")";
}
};
System.out.println(msg);
}
// 例外を投げる分岐は値を返さなくてよい
try {
int code = 9;
String name = switch (code) {
case 1 -> "新規";
case 2 -> "更新";
default -> throw new IllegalArgumentException("想定外のコード: " + code);
};
System.out.println(name);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
実行結果:
在庫なし
在庫 5 個(残りわずか)
在庫 120 個(十分)
想定外のコード: 9
後半のとおり、例外を投げる case は値を返さなくてもかまいません。 「想定していない値が来たら例外にする」という default は、switch 式でよく使う形です。
yieldの代わりにreturnを書くとどうなるか
switch 式のブロックの中で return を書くと、コンパイルエラーになります。
public class EYield {
public static void main(String[] args) {
int qty = 5;
String msg = switch (qty) {
case 0 -> "在庫なし";
default -> {
String level = qty >= 100 ? "十分" : "残りわずか";
return "在庫 " + level;
}
};
System.out.println(msg);
}
}
EYield.java:8: error: attempt to return out of a switch expression
return "在庫 " + level;
^
1 error
error: compilation failed
また、ブロックの中で yield を書かずに終わると、次のエラーになります。
EYield2.java:8: error: switch rule completes without providing a value
}
^
(switch rules in switch expressions must either provide a value or throw)
switch 式の各 case は「値を返す」か「例外を投げる」かのどちらか、とエラー文がそのまま教えてくれています。return はメソッドの値を返す文、yield は switch 式の値を返す文、と役割が分かれています。
つまずきやすい点とコンパイルエラー一覧
実際に出力されたエラー文をまとめます。日本語のメッセージは javac -J-Duser.language=ja で実行したときの出力です。
| エラー文(英語) | エラー文(日本語) | 原因 | 直し方 |
|---|---|---|---|
| selector type long is not allowed | (英語のまま表示) | long・float・double・boolean を switch に渡した | int に変換するか、if 文で書く |
| the switch expression does not cover all possible input values | switch式がすべての可能な入力値をカバーしていません | switch 式で case が足りない | 足りない case か default を足す |
| duplicate case label | caseラベルが重複しています | 同じ値の case が2つある | 重複を1つにまとめる |
| constant expression required | 定数式が必要です | case に通常の変数を書いた | リテラルか static final の定数にする |
| different case kinds used in the switch | switchでcaseの異なる種類が使用されています | コロン形式とアロー形式を1つの switch に混ぜた | どちらかにそろえる |
| attempt to return out of a switch expression | switch式の外側でreturnを実行しようとしています | switch 式の中で return を書いた | yield にする |
| switch rule completes without providing a value | switchルールが値を提供せずに完了しました | switch 式のブロックに yield がない | ブロックの最後に yield を書く |
| variable msg is already defined | 変数 msgはすでにメソッド main(String[])で定義されています | コロン形式の別々の case で同じ名前の変数を宣言した | case ごとに { } で囲むか、アロー形式にする |
caseに変数は書けない
case に書けるのは、コンパイル時に値が決まる定数です。ローカル変数を書くと constant expression required になります。
public class EConst {
public static void main(String[] args) {
int rank = 1;
int gold = 1;
switch (rank) {
case gold -> System.out.println("ゴールド");
default -> System.out.println("一般");
}
}
}
EConst.java:6: error: constant expression required
case gold -> System.out.println("ゴールド");
^
1 error
error: compilation failed
static final int GOLD = 1; のようにクラスの定数として宣言し、case GOLD -> と書くと、コンパイルが通って「ゴールド」と出力されることを確かめています。
コロン形式は変数のスコープがswitch全体になる
コロン形式では、switch のブロック全体が1つのスコープです。別々の case で同じ名前の変数を宣言すると、重複の扱いになります。
public class EScope {
public static void main(String[] args) {
int kind = 2;
switch (kind) {
case 1:
String msg = "一";
System.out.println(msg);
break;
case 2:
String msg = "二";
System.out.println(msg);
break;
}
}
}
EScope.java:10: error: variable msg is already defined in method main(String[])
String msg = "二";
^
1 error
error: compilation failed
case ごとに { } で囲むと、それぞれが別のスコープになり、同じ名前を使えます。
public class Scope {
public static void main(String[] args) {
int kind = 2;
// コロン形式は switch 全体で1つのスコープ。case ごとに { } で囲むと変数名を再利用できる
switch (kind) {
case 1: {
String msg = "一";
System.out.println(msg);
break;
}
case 2: {
String msg = "二";
System.out.println(msg);
break;
}
default:
System.out.println("その他");
}
}
}
実行結果:
二
アロー形式でブロックを書く場合は、最初から case ごとに { } で区切られるので、この点を気にせずに済みます。
ifとswitchの使い分け
| 状況 | 向いている書き方 |
|---|---|
| 1つの値を、決まった候補(定数)と比べる | switch |
| 候補が enum の定数 | switch 式(網羅性の確認が働く) |
| 「以上・未満」など範囲で判定する | if / else if |
| 複数の変数を組み合わせた条件 | if / else if |
| long・double・boolean で判定する | if / else if |
| 分岐が2つだけ | if / else、または条件演算子 |
そして switch の中での選び方は次のとおりです。
| 状況 | 選ぶ書き方 |
|---|---|
| 分岐の結果として値を1つ決める | アロー形式の switch 式 |
| 分岐ごとに処理を実行する(値は返さない) | アロー形式の switch 文 |
| Java 11 以前で動かす | コロン形式の switch 文(break を忘れずに。-Xlint:fallthrough で確認) |
| case の中が複数行になる | アローの右側をブロックにする(式なら yield で値を返す) |
新しく書くコードで Java 14 以降が使えるなら、アロー形式を基本にすると、break の書き忘れと変数のスコープの2つを気にせずに済みます。
よくある質問
Q. switch 文と switch 式の違いは何ですか?
A. switch 文は分岐ごとに処理を実行する構文で、値は返しません。switch 式は分岐の結果として値を返すので、String s = switch (x) { ... }; のように代入や return に使えます。switch 式はすべての入力に対応する case(または default)が必要です。
Q. break を書き忘れるとどうなりますか? A. コロン形式では、一致した case の下にある case の処理も続けて実行されます(フォールスルー)。この記事の例では「ゴールド」に続いて「シルバー」「ブロンズ」「一般」まで出力されました。アロー形式では起きません。
Q. yield はいつ使いますか?
A. switch 式で、case の右側を複数行のブロック { } にしたときに、そのブロックの値を返すために使います。右側が式1つなら不要です。コロン形式の switch 式でも yield で値を返します。
Q. switch で long や double は使えますか?
A. Java 21 では使えません(selector type long is not allowed などのコンパイルエラーになります)。long は int に変換できる範囲なら Math.toIntExact で変換し、double や boolean は if 文で書きます。
まとめ
- switch には「コロン形式/アロー形式」×「文/式」の4通りがある。 値を1つ決めるならアロー形式の switch 式がいちばん短い
- コロン形式は break を忘れると下の case まで実行される。 アロー形式は一致した case だけを実行する
- 使える型は byte・short・char・int とラッパー型、String、enum。 long・float・double・boolean はコンパイルエラー
- switch 式は網羅性が確認される。 enum の定数を書き漏らすと does not cover all possible input values になる
- null を渡すと NullPointerException。 default では受けられない。Java 21 以降は case null が使える
- yield は switch 式のブロックから値を返す文。 return を書くとコンパイルエラーになる
- 複数の値はカンマで並べる(
case 1, 2, 3 ->)。 アロー形式と switch 式は Java 14 以降
参考・出典
- OpenJDK「JEP 361: Switch Expressions」(JDK 14 で提供。アローのラベル、複数の定数、yield、網羅性): https://openjdk.org/jeps/361
- Oracle「The Java Language Specification, Java SE 21 Edition」第14章(14.11 The switch Statement): https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Javaのswitch文の使い方|switch式・アロー・yieldまで」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。