Java の switch 文は enum の網羅性をチェックしてくれない
はじめに
タイトル通り。これに気付かずしばらく悩んでしまったアホなオレを笑ってくれ。
switch 文
Java には太古の昔から switch 文ってのがある。
public static void judgment(int answer) {
switch (answer) {
case 42:
System.out.println("正解!");
break;
default:
System.out.println("残念!");
break;
}
}
まぁ break を忘れがちで危ないってことを除けば、ひときわ読みづらいって事もなく、そんなに悪いものでもない。
enum
比較的新しい機能なので[1]もしかしたらご存じないかもしれないが、Java には enum という定数を定義できる機能がある。
public enum SunVulcan {
EAGLE, SHARK, PANTHER
}
これの何が嬉しいかというと[2]、太陽戦隊サンバルカンにはバルイーグル、バルシャーク、バルパンサー[3]の 3 人しかいないことを保証できるってことだ[4]。
switch 文と enum
switch 文では enum を使うこともできる。
public static void printColor(SunVulcan mem) {
switch (mem) {
case EAGLE:
System.out.println("バルレッド!");
break;
case SHARK:
System.out.println("バルブルー!");
break;
case PANTHER:
System.out.println("バルイエロー!");
break;
}
}
なかなかに便利だ。
ところで、上に述べたように enum では定義した以外の定数は存在しないため、網羅性のチェックをしてくれてもよさそうなもんであるが、実際はしてくれない。
public static void printColor(SunVulcan mem) {
switch (mem) {
case EAGLE:
System.out.println("バルレッド!");
break;
case PANTHER:
System.out.println("バルイエロー!");
break;
}
}
この例ではバルシャークが忘れ去られているが、特に何の問題もなくコンパイル・実行できてしまう。哀れバルシャーク!
switch 式
つい先日[5]、switch 文に似て非なる switch 式という構文が使えるようになった。
public static void judgment(int answer) {
System.out.println(
switch (answer) {
case 42 -> "正解!";
default -> "残念!";
}
);
}
なんか -> とかあってラムダ式かよ!と思わなくもないが、ラムダ式ではない。switch 式と呼べ。
コイツはなんと break 不要なのだ。これで break を忘れたことによる徹夜のデバッグともおさらばだ。
しかも見ての通り、println の引数として switch 式を使うことができる。式なんだから当たり前だが。
switch 式と enum
当然 switch 式で enum を使うこともできる。
public static void printColor(SunVulcan mem) {
System.out.println(
switch (mem) {
case EAGLE -> "バルレッド!";
case SHARK -> "バルブルー!";
case PANTHER ->"バルイエロー!";
}
);
}
break も不要だし、めちゃめちゃスッキリした。
だが、switch 式のいいところはそこだけではない。次のコードは「バルシャークがいないぜ!」って言われて[6]コンパイルエラーになるのだ。
public static void printColor(SunVulcan mem) {
System.out.println(
switch (mem) {
case EAGLE -> "バルレッド!";
case PANTHER ->"バルイエロー!";
}
);
}
これならバルシャークも安心だ!
switch 文の新しい書き方
実は switch 式で出てきた書き方は switch 文でも使うこともできる。
public static void judgment(int answer) {
switch (answer) {
case 42 -> System.out.println("正解!");
default -> System.out.println("残念!");
}
}
こちらも break が不要だし、なかなか良い。
新しい書き方の switch 文と enum
もちろん新しい書き方の switch 文でも enum を使うことができる。
public static void printColor(SunVulcan mem) {
switch (mem) {
case EAGLE -> System.out.println("バルレッド!");
case SHARK -> System.out.println("バルブルー!");
case PANTHER -> System.out.println("バルイエロー!");
}
}
だが、switch 文の場合、新しい書き方で書いても enum の網羅性をチェックしてくれないのだ。
public static void printColor(SunVulcan mem) {
switch (mem) {
case EAGLE -> System.out.println("バルレッド!");
case PANTHER -> System.out.println("バルイエロー!");
}
}
このコードは特にコンパイルエラーにはならない。バルシャークの悲劇再び!
ぶっちゃけここを勘違いしていた。新しい書き方なら網羅性チェックしてくれるんじゃないかという淡い期待があったが、見事に裏切られた[7]。まあ、値を返さなくていいんだから言語仕様的には網羅性チェックは必要ないってことなんだろう。残念。
おまけ:古い書き方の switch 式
何のメリットがあるかは良く分からないが、実は switch 式を古い書き方で書くこともできる。
public static void judgment(int answer) {
System.out.println(
switch (answer) {
case 42:
yield "正解!";
default:
yield "残念!";
}
);
}
ちなみに break が無いが、switch 式では値を返す必要があるから普通は yield 文必須ってだけだ。yield 文が無ければ当たり前のようにフォールスルーするので注意。
なお、たとえ古い書き方で書いても switch 式であればバルシャークは忘れ去られないので安心だ。
おわりに
おのれの勘違いに気付いたのは、全 enum を網羅してる switch 文に対して JaCoCo に「デフォルトケースのテストが漏れてるぜ!」って言われたからだ。思わずバイトコードまで見てようやく switch 文は網羅性チェックとかしてないんだって事に気付いた。アホすぎる…
皆さんがオレのアホな勘違いを見て笑顔になってくれれば幸いである。
それでは、皆さんよい Java ライフを。
Discussion