String switchステートメントがnullケースをサポートしないのはなぜですか?


125

Java 7 switchステートメントがnullケースをサポートせず、代わりにスローするのはNullPointerExceptionなぜですか。以下のコメント付きの行を参照してください(例については、Javaチュートリアルの記事switchから引用しています)。

{
    String month = null;
    switch (month) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        //case null:
        default: 
            monthNumber = 0;
            break;
    }

    return monthNumber;
}

これにより、ifすべてのswitch使用前にnullチェックの条件が回避されます。


12
私たちはその言語を作った人ではないので、これに対する決定的な答えはありません。すべての答えは純粋な推測になります。
アステリ2013

2
スイッチをオンにしようとnullすると、例外が発生します。のifチェックを実行してnullから、switchステートメントに進みます。
gparyani 2013

28
JLSから:Javaプログラミング言語の設計者の判断では、[ 実行時にNullPointerException式が評価されるかどうかをスローするnull]は、switchステートメント全体を暗黙的にスキップするか、または後にステートメント(ある場合)を実行することを選択するよりも良い結果です。デフォルトのラベル(存在する場合)。
gparyani 2013

3
@gparyani:それを答えにしてください。それは非常に公式で決定的なようです。
Thilo 2013

7
@JeffGohlke:「あなたが決定を下した人でなければ、なぜ質問に答える方法はありません。」...まあ、gparyaniのコメントはそれ以外の場合は証明されます
user541686

回答:


145

damryfbfnetsiとしてポイントのコメントで、JLS§14.11は、以下の注意事項があります。

nullスイッチラベルとしての使用を禁止することで、実行できないコードを記述できなくなります。場合switchの式である参照型のものであるStringか、箱入りプリミティブ型または列挙型は、実行時エラーは、発現評価された場合に発生するnull実行時。Javaプログラミング言語の設計者の判断では、これはswitchステートメント全体を黙ってスキップしたり、defaultラベル(ある場合)の後にステートメント(ある場合)を実行することを選択したりするよりも良い結果です。

(強調鉱山)

最後の文はを使用する可能性をスキップしてcase null:いますが、それは合理的であり、言語設計者の意図についての見解を提供しています。

実装の詳細を見ると、Christian Hujerによるこのブログ投稿にnullは、スイッチでは許可されない理由に関する洞察に富んだ推測が含まれています(ただし、enumスイッチではなくスイッチを中心としていますString)。

switch内部的には、ステートメントは通常、tablesswitchバイトコードにコンパイルされます。そして、switchそのケースと同様に「物理的な」議論はintsです。スイッチをオンにするint値は、メソッドを呼び出すことによって決定されEnum.ordinal()ます。[...]序数はゼロから始まります。

ことを意味し、マッピングがnullする0良いアイデアではないでしょう。最初の列挙値のスイッチはnullと区別できません。列挙型の序数を1から数えるのは良い考えでしょう。しかし、そのように定義されておらず、この定義は変更できません。

一方でStringスイッチが別々に実装されenumスイッチは最初に来て、参照があるときに振る舞うべき参照型に切り替える方法については、先例を設定しましたnull


1
それがにcase null:のみ実装されている場合、null処理をaの一部として許可することは大きな改善でしたString。現在のようにString文字列定数を最初に置くことによって暗黙的に行われる場合がほとんどですが、現在すべてのチェックは、それを修正したい場合はとにかくnullチェックが必要"123test".equals(value)です。今私たちは次のようにswitch文を書くことを余儀なくされていますif (value != null) switch (value) {...
ヨーヨー

1
「nullを0にマッピングするのは良い考えではありません」と言います。「」。hashcode()の値は0なので、これは控えめな表現です。これは、null文字列と長さ0の文字列をswitchステートメントで同じように処理する必要があることを意味しますが、これは明らかに実行不可能です。
skomisa

列挙型の場合、nullから-1へのマッピングを妨げているのは何ですか?
クリスピー

31

一般的nullに扱いが厄介です。多分よりよい言語はなしで生きることができnullます。

あなたの問題は

    switch(month==null?"":month)
    {
        ...
        //case "":
        default: 
            monthNumber = 0;

    }

month空の文字列の場合はお勧めできません。これにより、空の文字列と同じように扱われます。
gparyani 2013

13
多くの場合、nullを空の文字列として扱うことは完全に合理的です
エリックウッドラフ2014

23

きれいではありませんString.valueOf()が、スイッチでnull文字列を使用できます。が見つかるとnull、に変換し"null"ます。それ以外の場合は、渡したのと同じ文字列を返します。"null"明示的に処理しない場合は、に移動しdefaultます。唯一の注意点は、文字列"null"と実際のnull変数を区別する方法がないことです。

    String month = null;
    switch (String.valueOf(month)) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        case "null":
            monthNumber = -1;
            break;
        default: 
            monthNumber = 0;
            break;
    }
    return monthNumber;

2
Javaでこのようなことをするのはアンチパターンだと思います。
ルカシュRzeszotarski

2
@ŁukaszRzeszotarskiそれは私が「それはきれいではない」と私が言った意味とほぼ同じです
krispy

15

これはなぜスローするのかを答えようとする試みです NullPointerException

以下のjavapコマンドの出力は、引数文字列のcaseハッシュコードに基づいて選択されていることを示しているswitchため.hashCode()、null文字列に対してが呼び出されるとNPEをスローします。

6: invokevirtual #18                 // Method java/lang/String.hashCode:()I
9: lookupswitch  { // 3
    -1826660246: 44
     -263893086: 56
      103666243: 68
        default: 95
   }

つまり、JavaのhashCodeに対する回答に基づいて、異なる文字列に対して同じ値を生成できますか?まれですが、2つのケースが一致する可能性はまだあります(同じハッシュコードの2つの文字列)以下の例を参照してください

    int monthNumber;
    String month = args[0];

    switch (month) {
    case "Ea":
        monthNumber = 1;
        break;
    case "FB":
        monthNumber = 2;
        break;
    // case null:
    default:
        monthNumber = 0;
        break;
    }
    System.out.println(monthNumber);

そのためのjavap

  10: lookupswitch  { // 1
              2236: 28
           default: 59
      }
  28: aload_3       
  29: ldc           #22                 // String Ea
  31: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  34: ifne          49
  37: aload_3       
  38: ldc           #28                 // String FB
  40: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  43: ifne          54
  46: goto          59 //Default

あなたが見ることができるように一つだけの場合は、のために生成されます"Ea""FB"が、2つのを持つif各ケース文字列との一致をチェックするための条件。この機能を実装する非常に興味深い複雑な方法!


5
ただし、別の方法で実装することもできます。
ティロ2013

2
これは設計上のバグだと思います。
ディアハンター

6
これが、文字列ハッシュ関数のいくつかの怪しげな側面が変更されていない理由と関係があるのだろうか:一般に、コードはhashCodeプログラムの異なる実行で同じ値を返すことに依存すべきではありませんが、コンパイラーでは、文字列ハッシュ方式は言語仕様の一部になります。
スーパーキャット2013

5

短い話...(そしてうまくいけば十分に面白い!!!)

Enumは最初にJava1.5で導入され(20049月)、Stringのスイッチ許可することを要求するバグは長い間(9510月)提出されました。2004年6月にそのバグに投稿されたコメントを見ると、彼らはこのバグを据え置いた(無視した)ように見え、最終的に同じ年にJava 1.5を起動し、序数が0から始まるenumを導入して決定しました(ミス)列挙型のnullをサポートしません。その後、Java1.720117月)で(強制的にDon't hold your breath. Nothing resembling this is in our plans.)Stringと同じ哲学(つまり、バイトコードの生成中は、hashcode()メソッドを呼び出す前にnullチェックは実行されませんでした)。

つまり、列挙型が最初に来て、序数が0で始まるように実装されたため、スイッチブロックでnull値をサポートできず、後でStringを使用して、同じ哲学、つまりnull値を強制しないことにしました。スイッチブロックで許可されています。

TL; DR Stringを使用すると、Javaコードからバイトコードへの変換を実装するときにNPE(nullのハッシュコードを生成しようとしたため)を処理できますが、最終的にはしないことにしました。

参照: TheBUGJavaVersionHistoryJavaCodeToByteCodeSO


1

Javaドキュメントによると:

スイッチは、byte、short、char、およびintプリミティブデータ型で機能します。また、列挙型(列挙型で説明)、Stringクラス、および特定のプリミティブ型をラップするいくつかの特別なクラス(文字、バイト、Short、および整数(数値と文字列で説明))でも機能します。

nullは型がなく、何のインスタンスでもないため、switchステートメントでは機能しません。


4
そして、まだnullのための有効な値であるStringCharacterByteShort、またはInteger参照。
アステリ2013

0

答えは、参照型(ボックス化されたプリミティブ型など)でスイッチを使用する場合、式をnullにすると、ボックス化を解除するとNPEがスローされるため、実行時エラーが発生するということです。

したがって、ケースnull(これは違法です)は決して実行できません;)


1
ただし、別の方法で実装することもできます。
Thilo 2013

OK @Thilo、私より賢い人がこの実装に関与しました。これが実装された可能性のある他の方法を知っている場合は、それらが何であるかを知りたいと思います[そして他にもあると思います]共有してください...
amrith

3
文字列はボックス化されたプリミティブタイプではなく、誰かが「ボックス化解除」しようとしているため、NPEは発生しません。
Thilo 2013

@thilo、これを実装する他の方法は何ですか?
amrith 2013

3
if (x == null) { // the case: null part }
Thilo 2013

0

@Paul Belloraの回答のhttps://stackoverflow.com/a/18263594/1053496にある洞察に満ちたコメント(ボンネットの下...)に同意します。

私の経験からもう1つ理由を見つけました。

'case'がnullである可能性がある場合、つまり、switch(variable)がnullであることを意味します。開発者が対応する 'null'ケースを提供する限り、問題はありません。しかし、開発者が一致する 'null'ケースを提供しない場合はどうなりますか。次に、それを「デフォルト」のケースと一致させる必要があります。したがって、「null」をデフォルトに一致させると、「驚くべき動作」が発生する可能性があります。したがって、「NPE」をスローすると、開発者はすべてのケースを明示的に処理するようになります。この場合、NPEを投げることは非常に慎重であることがわかりました。


0

Apache StringUtilsクラスを使用する

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.