web-dev-qa-db-ja.com

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

なぜこれは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に置き換えて、nullbooleanにアンボックス化されないようにすることです。これは不可能です。しかし、それは問題ではありません。質問はwhy?特に2番目のケースについて、この動作を確認する参照がJLSにありますか?

128
BalusC

違いは、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オペランドのタイプはそれぞれBooleanおよびbooleanであるため、この句が適用されます。

    2番目と3番目のオペランドのいずれかがブール型で、もう一方のタイプがブール型の場合、条件式のタイプはブール型になります。

    式のタイプはbooleanであるため、第2オペランドはbooleanに強制する必要があります。コンパイラは、自動アンボックス化コードを第2オペランド(returnsNull()の戻り値)に挿入して、booleanと入力します。これにより、実行時に返されるnullからNPEが発生します。

  • E2の場合、2番目と3番目のオペランドの型は_<special null type>_(E1!のBooleanではありません)とbooleanであるため、特定の入力句は適用されません( go 'em! )を読んでください。したがって、最後の「その他の」節が適用されます。

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

    • 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_のように自動アンボックス化を必要としないため、nullが返されたときに自動アンボックス化NPEはありません。


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

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

90
Bert F

この線:

_    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; 
_
24
jjungnickel

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

  • 2番目と3番目のオペランドのいずれかがブール型で、もう一方のタイプがブール型の場合、条件式のタイプはブール型になります。

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

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

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

この問題はバイトコードからわかります。メインのバイトコードの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
0
Yanhui Zhou