なぜこれは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
}
?
解決策は、false
をBoolean.FALSE
に置き換えて、null
がboolean
にアンボックス化されないようにすることです。これは不可能です。しかし、それは問題ではありません。質問はwhy?特に2番目のケースについて、この動作を確認する参照がJLSにありますか?
違いは、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)に適用した結果です。
<special null type>
_( §4.1 を参照)boolean
<special null type>
_( §5.1.7 のボクシング変換のリストの最後の項目を参照)Boolean
したがって、条件式のタイプはBoolean
であり、第3オペランドはBoolean
に強制する必要があります。コンパイラは、第3オペランド(false
)にオートボクシングコードを挿入します。第2オペランドは_E1
_のように自動アンボックス化を必要としないため、null
が返されたときに自動アンボックス化NPEはありません。
この質問には、同様のタイプ分析が必要です。
この線:
_ 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番目と3番目のオペランドのいずれかがブール型で、もう一方のタイプがブール型の場合、条件式のタイプはブール型になります。
したがって、最初の例では、最初のルールに従ってBoolean
をboolean
に変換するためにBoolean.booleanValue()
を呼び出そうとします。
2番目のケースでは、1番目のオペランドがnull型で、2番目のオペランドが参照型ではないため、オートボクシング変換が適用されます。
- それ以外の場合、第2および第3オペランドはそれぞれS1およびS2型です。 T1をボクシング変換をS1に適用した結果の型とし、T2をボクシング変換をS2に適用した結果の型とします。条件式のタイプは、キャプチャ変換(§5.1.10)をlub(T1、T2)(§15.12.2.7)に適用した結果です。
この問題はバイトコードからわかります。メインのバイトコードの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