ブール、条件演算子、オートボクシング


132

なぜこれがスローされるのですか NullPointerException

public static void main(String[] args) throws Exception {
    Boolean b = true ? returnsNull() : false; // NPE on this line.
    System.out.println(b);
}

public static Boolean returnsNull() {
    return null;
}

これはしませんが

public static void main(String[] args) throws Exception {
    Boolean b = true ? null : false;
    System.out.println(b); // null
}

解決策は、ボックス化falseBoolean.FALSEれないようnullにするためにで置き換える方法です- booleanこれは不可能です。しかし、それは問題ではありません。問題はなぜですか?特に2番目のケースのこの動作を確認するJLSのリファレンスはありますか?


28
うわー、オートボクシングは...ええと... Javaプログラマーの驚きの無限のソースですよね?:-)
leonbloy

私も同様の問題を抱えていましたが、OpenJDK VMでは失敗しましたが、HotSpot VMでは機能しました。
kodu 2017

回答:


92

違いは、returnsNull()メソッドの明示的な型がコンパイル時の式の静的型付けに影響することです。

E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)

E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)

Java言語仕様のセクション15.25条件演算子を参照してください。:

  • E1の場合は、2番目と3番目のオペランドの種類があるBooleanboolean、それぞれので、この条項が適用されます。

    2番目と3番目のオペランドの一方がブール型で、もう一方の型がブール型である場合、条件式の型はブール型です。

    式のタイプはbooleanであるため、第2オペランドはに強制変換する必要がありますboolean。コンパイラーは、第2オペランド(の戻り値returnsNull())に自動アンボックス化コードを挿入して、それをtypeにしbooleanます。もちろん、これはnull実行時に返されたNPEを引き起こします。

  • E2の場合、2番目と3番目のオペランドの型がある<special null type>(ないBoolean!E1のように)とboolean、それぞれ具体的なタイピング句が適用されないので(!」読み取りEMを行く)、最終ので、 『それ以外』条項が適用されます。

    それ以外の場合、2番目と3番目のオペランドのタイプはそれぞれS1とS2です。T1をS1にボクシング変換を適用した結果のタイプとし、T2をS2にボクシング変換を適用した結果のタイプとします。条件式のタイプは、lub(T1、T2)(§15.12.2.7)にキャプチャ変換(§5.1.10)を適用した結果です。

    • S1 == <special null type>§4.1を参照)
    • S2 == boolean
    • T1 == box(S1)== <special null type>§5.1.7のボクシング変換のリストの最後の項目を参照 )
    • T2 == box(S2)== `ブール
    • lub(T1、T2)== Boolean

    したがって、条件式のタイプはでBooleanあり、第3オペランドはに強制変換する必要がありますBoolean。コンパイラーは、第3オペランド(false)の自動ボックス化コードを挿入します。2番目のオペランドはのように自動ボックス化解除を必要としないため、が返されたE1ときに自動ボックス化解除NPE はありませんnull


この質問には、同様のタイプの分析が必要です。

Java条件演算子?:結果タイプ


4
理にかなっていると思います。§15.12.2.7は苦痛です。
BalusC 2010年

それは簡単です...しかし、後から見るだけです。:-)
Bert F

@BertFの機能lubは何をlub(T1,T2)表していますか?
オタク2014

1
@Geek-lub()-最小上限-基本的に、それらに共通する最も近いスーパークラス。null(「特殊なnull型」型)は暗黙的に任意の型に変換(拡大)できるため、lub()の目的では、特殊なnull型を任意の型(クラス)の「スーパークラス」と見なすことができます。
バートF

25

この線:

    Boolean b = true ? returnsNull() : false;

内部的に次のように変換されます。

    Boolean b = true ? returnsNull().booleanValue() : false; 

ボックス化解除を実行します。したがって:null.booleanValue()NPEが生成されます

これは、オートボクシングを使用する際の主な落とし穴の1つです。この動作は確かに5.1.8 JLSで文書化されています

編集:私はボックス化解除がブール型の3番目の演算子によるものだと信じています、(暗黙のキャストが追加されました):

   Boolean b = (Boolean) true ? true : false; 

2
最終的な値がブール型オブジェクトである場合、なぜそれがそのように開梱しようとするのですか?
エリックロバートソン

16

Java言語仕様、セクション15.25

  • 2番目と3番目のオペランドの一方がブール型で、もう一方の型がブール型である場合、条件式の型はブール型です。

したがって、最初の例では、最初のルールに従ってBoolean.booleanValue()に変換Booleanするために呼び出しを試みbooleanます。

2番目のケースでは、最初のオペランドがnull型で、2番目のオペランドが参照型ではないため、オートボクシング変換が適用されます。

  • それ以外の場合、2番目と3番目のオペランドのタイプはそれぞれS1とS2です。T1をS1にボクシング変換を適用した結果のタイプとし、T2をS2にボクシング変換を適用した結果のタイプとします。条件式のタイプは、lub(T1、T2)(§15.12.2.7)にキャプチャ変換(§5.1.10)を適用した結果です。

これは最初のケースには答えますが、2番目のケースには答えません。
BalusC 2010年

おそらく、値の1つがの場合に例外がありますnull
エリックロバートソン

@エリック:JLSはこれを確認しますか?
BalusC 2010年

1
@エリック:booleanは参照型ではないため、適用できるとは思いません。
axtavt

1
そして、私が追加してもいいです...これが、必要に応じて明示的な呼び出しを使用して、3項の両側を同じ型にする必要がある理由です。仕様を覚えていて、何が起こるかわかっているとしても、次に来てあなたのコードを読むプログラマーはそうでないかもしれません。私の控えめな意見では、普通の人間が予測するのが難しいことをするよりも、コンパイラがこれらの状況でエラーメッセージを生成した方がいいでしょう。ええと、その振る舞いが本当に役に立つ場合もあるかもしれませんが、私はまだそれをヒットしていません。
ジェイ

0

この問題はバイトコードからわかります。メインのバイトコードの3行目3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z、値nullのボクシングブール値invokevirtual、メソッドでjava.lang.Boolean.booleanValue、もちろんNPEがスローされます。

    public static void main(java.lang.String[]) throws java.lang.Exception;
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=2, args_size=1
           0: invokestatic  #2                  // Method returnsNull:()Ljava/lang/Boolean;
           3: invokevirtual #3                  // Method java/lang/Boolean.booleanValue:()Z
           6: invokestatic  #4                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
           9: astore_1
          10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
          13: aload_1
          14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          17: return
        LineNumberTable:
          line 3: 0
          line 4: 10
          line 5: 17
      Exceptions:
        throws java.lang.Exception

    public static java.lang.Boolean returnsNull();
      descriptor: ()Ljava/lang/Boolean;
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=1, locals=0, args_size=0
           0: aconst_null
           1: areturn
        LineNumberTable:
          line 8: 0
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.