1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Javaのswitch文とswitch式はどう区別する?値を代入しないアロー構文の正体

1
Posted at

Java 14で正式導入されたアロー構文(->)を伴う switch は、従来の switch 文に比べて記述がシンプルになりました。しかし、そのシンプルさゆえに「今書いているのは switch 文なのか、それとも switch 式なのか」という区別で迷うケースがあります。

先日、Qiitaの質問機能で以下のような興味深いコードと疑問が投稿されていました。
URL: https://qiita.com/hiroki-shosinsher/questions/ddfb8eeeaf9e38c70a4a

この記事では、Java言語仕様(JLS: Java Language Specification、Javaの文法を定義する公式仕様書)の原文と、実際のコンパイル結果に基づいて、この switch の正体を解き明かします。

対象読者

  • アロー構文の switch を使ったことはあるが、「文」と「式」の区別を意識したことがない方
  • 「式(Expression)」と「文(Statement)」というJavaの基本概念をなんとなく知っている方(記事中でも都度補足します)

検証環境

  • OpenJDK 21.0.11(eclipse-temurin:21)

議論の対象となったコード

public class Sample{
    public static void main(String[] args){
        int a = 2;
        switch(a){
            case 1 -> System.out.println("A");
            case 2 -> System.out.println("B");
            case 3 -> System.out.println("C");
            default -> System.out.println("D");
        };  // ← この閉じカッコ直後のセミコロンにも注目
    }
}

このコードについて、以下のような疑問が生じていました。

  1. 変数の代入などに使っておらず、値を返していない(画面表示のみ)から switch 文ではないか?
  2. 閉じカッコの後ろのセミコロン(;)は switch 式の証拠か、それとも switch 文に余計なセミコロンがついているだけか?
  3. 全体をカッコで囲むとコンパイルエラーになるのは、これが「文」だからではないか?

結論:これは「アロー構文を使ったswitch文」+「空文」である

言語仕様に照らすと、このコードの正体は次のとおりです。

  • switch(a){ ... } の部分は、アロー構文を使った switch文(SwitchStatement)
  • 閉じカッコ直後の ; は、switch式の証拠ではなく、単なる 空文(Empty Statement、JLS §14.6)。何もしない無害な文です

つまり冒頭の疑問1の「これは switch 文ではないか?」は結論として正しいです。ただしその理由は「値を代入していないから」ではなく、「値が要求されない文脈に置かれているから」です。この違いが本記事のポイントになります。

なぜそう言えるのか、仕様書の定義とコンパイル実験で1つずつ確認していきます。

根拠1:switch式は単体で「文」になれない

本題に入る前に、2つの用語を整理しておきます。

  • 式(Expression):評価すると値を生み出す構文。1 + 2"A".length() など
  • 文(Statement):処理の実行単位となる構文。if 文や return 文など

Javaでは、式を単体で文として置けるのは、ごく限られた種類だけです。これを 式文(ExpressionStatement) と呼びます。JLS §14.8 で式文にできる式は以下に限定されています。

StatementExpression:

  • Assignment(代入式)
  • PreIncrementExpression / PreDecrementExpression(前置インクリメント・デクリメント)
  • PostIncrementExpression / PostDecrementExpression(後置インクリメント・デクリメント)
  • MethodInvocation(メソッド呼び出し式)
  • ClassInstanceCreationExpression(クラスインスタンス生成式)

このリストに SwitchExpression(switch式)は含まれていません

したがって「void的なswitch式を文の位置に単体で置いている」という解釈は文法上成立しません。文が要求される位置に単体で現れた switch は、必ず switch文 として解釈されます。

根拠2:そもそも「値を返さないswitch式」は存在しない

「値を代入していないだけで、実はvoidなswitch式なのでは?」という解釈も考えられますが、これも仕様が明確に否定しています。

JLS §15.28.1 では、switch式の各アームが生み出す値を「結果式(Result Expression)」と呼び、次のように規定しています。

It is a compile-time error if a switch expression has no result expressions.
(switch式に結果式が1つもない場合、コンパイルエラーとなる)

そして System.out.println(...) のようなvoidメソッドの呼び出し式は「値を生み出さない」ため、結果式になれません。実際に代入してswitch式として解釈させようとすると、コンパイルエラーになります。

// 実験:代入してswitch式として解釈させてみる
Object x = switch(a){
    case 1 -> System.out.println("A");
    default -> System.out.println("D");
};
error: incompatible types: bad type in switch expression
            case 1 -> System.out.println("A");
                                        ^
    void cannot be converted to Object

「voidをObjectに変換できない」、つまりswitch式の結果としてvoidは認められないことがエラーメッセージからも確認できます。値を返さないswitchが書ける時点で、それはswitch式ではなくswitch文なのです。

根拠3:アローの右側の書き方では「文」と「式」を区別できない

-> の右側の書き方を見れば区別できるのでは?」と思うかもしれませんが、実はできません。アロー構文の文法(SwitchRule)は、switch文(JLS §14.11.1)とswitch式(JLS §15.28)で共通だからです。

SwitchRule:

  • SwitchLabel -> Expression ;
  • SwitchLabel -> Block
  • SwitchLabel -> ThrowStatement

注目すべきは1行目です。アローの右側に式を書く場合、switch文でもswitch式でも末尾のセミコロンは文法上必須です。試しに省略すると、switch文であってもエラーになります。

error: ';' expected
            case 1 -> System.out.println("A")
                                             ^

つまり、各 case の行末にセミコロンがあるかどうかは、文と式を見分ける材料にはなりません。

なお、switch文の場合は追加の制約として「アローの右側の式は式文にできる式(StatementExpression)に限る」という規定があります(JLS §14.11.2)。今回の System.out.println(...) はメソッド呼び出し式なので、この制約も満たしています。

では、閉じカッコの後ろのセミコロンの正体は?

ここまでで switch(a){ ... } がswitch文だと確定しました。switch文の文法は switch ( Expression ) SwitchBlock であり、末尾にセミコロンを含みません

では }; のセミコロンは何かというと、空文(Empty Statement、JLS §14.6 です。セミコロン単体は「何もしない文」として合法なので、コンパイルエラーにはなりません。if (...) { ... }; の末尾に付いたセミコロンと同じ、消しても挙動が変わらない余計な1文字です。

カッコ ( ) で囲むとエラーになる理由

最後に、疑問3の「カッコで囲むとエラーになるのは文だから?」を検証します。

(switch(a){
    case 1 -> System.out.println("A");
    default -> System.out.println("D");
});
error: not a statement
        (switch(a){
        ^

カッコ ( ) の中に書けるのは「式」だけです。そのためカッコで囲むと、switchは式として解釈することを強制されます。しかし、

  1. 根拠2のとおり、結果式がすべてvoidのswitch式はそもそも不正
  2. 仮に式として成立しても、「カッコで囲まれた式(Parenthesized Expression)」は根拠1の式文リストに含まれないため、文の位置には置けない

という二重の理由でエラーになります。「文だからカッコで囲めない」という直感は結果的に近いところを突いていますが、正確には「カッコによって式としての解釈を強制した結果、式として成立できなかった」というのが実態です。

まとめ:見分け方はただ1つ「値が要求される文脈かどうか」

3つの疑問への回答をまとめます。

疑問 回答
1. 値を返していないからswitch文では? 結論は正しい。ただし正確な理由は「値が要求されない文脈(文の位置)にあるから」
2. 末尾のセミコロンはswitch式の証拠? いいえ。単なる空文。消しても挙動は変わらない
3. カッコで囲むとエラーになるのは文だから? 半分正解。カッコは式としての解釈を強制し、その解釈が成立しないためエラーになる

アロー構文・各caseのセミコロン・末尾のセミコロンは、いずれも文と式の判別材料になりません。見分け方はただ1つ。

そのswitchが「値を要求される文脈」(代入の右辺、メソッドの引数、return文など)に置かれていれば式、そうでなければ文。

これだけです。コンパイラもこの文脈によって解釈を切り替えています。

【おまけ】実務上の最大の違いは「網羅性」

「文か式かなんて、結局ラベルの違いでは?」と思うかもしれませんが、実務に直結する大きな違いが1つあります。網羅性(exhaustiveness)チェック、つまり default を省略できるかどうかです。

switchは「必ず値を生み出す」必要があるため、すべての入力をカバーしなければならず、default(またはenum全定数などの網羅)が必須です。

// switch式:defaultを省略するとコンパイルエラー
String s = switch(a){
    case 1 -> "A";
};
// error: the switch expression does not cover all possible input values

一方、今回のような int をセレクタとするswitchは、アロー構文であっても網羅性を要求されません。

// switch文:defaultなしでもコンパイル成功
switch(a){
    case 1 -> System.out.println("A");
    case 2 -> System.out.println("B");
}

※ ただしJava 21以降、case に型パターン・レコードパターン・null を使う場合、またはセレクタが従来のswitchで扱えなかった型(Object などの型)の場合は、switchでも網羅性が必須になります(enhanced switch statement、JLS §14.11.2)。String やラッパー型・enumをセレクタに定数の case だけを書く従来型のswitch文は、引き続き網羅性を要求されません。

つまり「文か式か」は単なるラベルの違いではなく、「default を書き忘れたときにコンパイラが守ってくれるかどうか」という実用上の違いとして現れます。アロー構文を使うときは、自分が書いているのが文なのか式なのかを意識しておくと、コンパイルエラーの意味がすっと理解できるようになります。

おわりに

実はこの記事を書き始めた段階では、私は「これはvoid型のswitch式を式文として書いたもの」という推測を立てていました。しかしJLSの原文を確認すると、根拠にしようとした規定(式文リストへのSwitch式の包含や、void型switch式の定義)はどこにも存在せず、コンパイル実験でも否定されました。

仕様の原文と実際のコンパイラで裏を取ることが大切でした。本記事の引用元は以下のとおりですので、ぜひみなさんも確認してみてください。

参考(JLS SE 21)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?