web-dev-qa-db-ja.com

列挙型のスイッチにデフォルトが必要なのはなぜですか?

通常、switchステートメントではデフォルトは必要ありません。ただし、次の状況では、デフォルトのステートメントのコメントを外したときにのみ、コードが正常にコンパイルされます。なぜ誰かが説明できますか?

public enum XYZ {A,B};
public static String testSwitch(XYZ xyz)
{
    switch(xyz)
    {
    case A:
        return "A";
    case B:
    //default:
        return "B";
    }
}
39
apoorv020

defaultのコメントを外す必要がある理由は、関数がStringを返すと関数が言っているためですが、caseラベルのみがAに対して定義されている場合Bの場合、他に何かを渡しても、関数は値を返しません。 Javaは、値を返すことを示すすべての関数が実際にすべての可能な制御パスの値を返すことを要求します。あなたの場合、コンパイラはすべての可能な入力が値を返すと確信していません。

この理由は、enumのすべてのケースをカバーしたとしても、コードが失敗する可能性があるためだと私は信じています(これについては確信がありません)。特に、Javaこのswitchステートメントを含むコード(これは正常に機能します)をコンパイルし、後でenumを変更して3番目の定数が存在するようにしたとします。 Cと言いますが、switchステートメントを含むコードを再コンパイルしません。ここで、前のコードを使用するJavaコードを記述しようとすると、コンパイルされたクラスとCをこのステートメントに渡すと、コードは返す値がなくなり、すべての関数が常に値を返すというJavaコントラクトに違反します。

より技術的に言えば、本当の理由は、JVMバイトコードベリファイアが常に、関数の終わりから抜け落ちる制御パスがある関数を拒否することだと思います(§4.9.2を参照してください JVM仕様の )、そしてコードがコンパイルされた場合、それはとにかく実行時にJVMによって拒否されるだけです。したがって、コンパイラーはエラーを表示して、問題が存在することを報告します。

50
templatetypedef

これは、switchステートメントのJLS確定割り当てルール( JLS 16.2.9 )によって説明され、次のように説明されていると思います。

"Vは、以下のすべてが当てはまる場合、switchステートメントの後に割り当てられます:

  • スイッチブロックにデフォルトラベルがあるか、スイッチ式の後にVが[un]割り当てられています。

次に、これをメソッドの戻り値である概念的なVに適用すると、defaultブランチがない場合、値は概念的に割り当てられていないことがわかります。

わかりました...私は戻り値をカバーするために明確な割り当てルールを外挿していますが、おそらくそうではありません。しかし、仕様でもっと直接的なものを見つけることができなかったからといって、それがそこにないわけではありません:-)


コンパイラーがエラーを出さなければならないもう1つの理由(より健全な)があります。これはenumJLS 13.4.26 )のバイナリ互換性規則に由来し、次のように述べられています。

"列挙型から定数を追加または並べ替えても、既存のバイナリとの互換性は損なわれません。"

この場合、それはどのように適用されますか?コンパイラwasが、OPのスイッチ例のステートメントが常に何かを返したと推測できると仮定します。プログラマがenumを変更して定数を追加するとどうなりますか? JLSバイナリ互換性ルールによると、バイナリ互換性は壊れていません。しかし、switchステートメントを含むメソッドは、(その引数に応じて)未定義の値を返すことができます。これはできませんが発生するため、スイッチは必ずコンパイルエラー。


Java 12で、スイッチ式を含むスイッチの拡張機能が導入されました。これは、コンパイル時とランタイムの間で変化する列挙型と同じ問題に遭遇します。 JEP 354 によると、 =、彼らはこの問題を次のように解決します:

スイッチ式のケースは網羅的でなければなりません。可能なすべての値に対して、一致するスイッチラベルが必要です。 (明らかに、switchステートメントは網羅的である必要はありません。)

実際には、これは通常、デフォルトの句が必要であることを意味します。ただし、すべての既知の定数をカバーする列挙型スイッチ式の場合、コンパイラーによってデフォルト句が挿入され、列挙型定義がコンパイル時と実行時の間で変更されたことを示します。この暗黙的なデフォルト句の挿入に依存すると、コードがより堅牢になります。コードが再コンパイルされると、コンパイラーはすべてのケースが明示的に処理されることを確認します。開発者が明示的なデフォルト句を挿入した場合(今日のように)、考えられるエラーは非表示になります。

明確でない唯一のことは、暗黙のデフォルト句が実際に行うことです。私の推測では、チェックされていない例外がスローされます。 (現時点では、Java 12のJLSは、新しいスイッチ式を説明するように更新されていません。)

43
Stephen C

すでに述べたように、値を返す必要があり、コンパイラは列挙が将来変更できないとは想定していません。例えば。 enumの別のバージョンを作成し、メソッドを再コンパイルせずにそれを使用できます。

注:nullであるxyzには3番目の値があります。

public static String testSwitch(XYZ xyz) {
    if(xyz == null) return "null";
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    return xyz.getName();
}

これは同じ結果です

public static String testSwitch(XYZ xyz) {
     return "" + xyz;
}

リターンを回避する唯一の方法は、例外をスローすることです。

public static String testSwitch(XYZ xyz) {
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    throw new AssertionError("Unknown XYZ "+xyz);
}
7
Peter Lawrey

Java 12では、プレビュースイッチ式機能( JEP-325 )を次のように使用できます。

public static String testSwitch(XYZ xyz) {
    return switch (xyz) {
        case A -> "A";
        case B -> "B";
    };
}

スイッチですべての列挙値を処理する限り、デフォルトの大文字小文字は必要ありません。

プレビュー機能を使用するには、javacおよびJava--enable-preview --source 12オプションを渡す必要があります。

3
Adrian

このメソッドが例外をスローしない限り、文字列を返すとの規約があります。そしてeverytimeは、xyzの値がXVZ.AまたはXYZ.Bに等しい場合に限定されません。

obviuosである別の例を次に示します。コードは正しく実行されますが、同じ理由でコンパイル時エラーが発生します。

public boolean getTrue() {
  if (1 == 1) return true;
}

する必要がある既定のステートメントを追加するのは正しくありません。いつでも値を返す必要があるのは事実です。したがって、デフォルトのステートメントを追加するか、switchブロックの後にreturnステートメントを追加します。

1
Andreas_D
default: throw new AssertionError();
0
irreputable

コンパイラはenumに値が2つしかないことを推測できないため、メソッドから値を返すように強制します。 (しかし、なぜそれが推測できないのか私にはわかりません、おそらくそれは反射を持つものを持っています)。

0