ブール値、条件付き演算子、自動ボクシング
-
28-09-2019 - |
質問
なぜこれが投げるのですか 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
- それは不可能です。しかし、それは問題ではありません。質問は どうして? JLSには、この動作、特に2番目のケースを確認する参照はありますか?
解決
違いは、明示的なタイプのものです 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番目のオペランドの1つが型ブール値であり、他のオペランドのタイプが型ブール値である場合、条件付き式のタイプはブール値です。
式のタイプはあるので
boolean
, 、2番目のオペランドは強制されなければなりませんboolean
. 。コンパイラは、2番目のオペランドに自動不動のコードを挿入します(returnsNull()
)タイプにするためboolean
. 。これはもちろん、からNPEを引き起こしますnull
実行時に返されます。E2の場合、2番目と3番目のオペランドのタイプは
<special null type>
(いいえBoolean
E1のように!)そしてboolean
それぞれ、特定のタイピング条項が適用されません('emを読んでください!)、したがって、最終的な「それ以外の」節が適用されます。それ以外の場合、2番目と3番目のオペランドは、それぞれS1とS2のタイプです。 T1を、ボクシング変換をS1に適用してから生じるタイプとし、T2をS2にボクシング変換を適用することから生じるタイプとします。条件付き式のタイプは、キャプチャ変換(§5.1.10)をラブ(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)== `boolean
- LUB(T1、T2)==
Boolean
したがって、条件付き式のタイプはです
Boolean
そして、3番目のオペランドは強制されなければなりませんBoolean
. 。コンパイラは、3番目のオペランドの自動ボクシングコードを挿入します(false
)。 2番目のオペランドでは、Auto-Unboxingが必要ではありませんE1
, 、したがって、自動不動のNPEはありませんnull
返されます。- S1 ==
この質問には、同様のタイプ分析が必要です。
他のヒント
この線:
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番目のオペランドの1つが型ブール値であり、他のオペランドのタイプが型ブール値である場合、条件付き式のタイプはブール値です。
したがって、最初の例は呼び出そうとします Boolean.booleanValue()
変換するために Boolean
に boolean
最初のルールに従って。
2番目のケースでは、最初のオペランドはnullタイプであり、2番目のオペランドが参照タイプでない場合、自動ボクシング変換が適用されます。
- それ以外の場合、2番目と3番目のオペランドは、それぞれS1とS2のタイプです。 T1を、ボクシング変換をS1に適用してから生じるタイプとし、T2をS2にボクシング変換を適用することから生じるタイプとします。条件付き式のタイプは、キャプチャ変換(§5.1.10)をラブ(T1、T2)に適用した結果です(§15.12.2.7)。
この問題はBYTEコードから見ることができます。メインのバイトコードの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