次の Java コードを実行します。
boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1.doubleValue() : d2;
NullPointerException があるのはなぜですか?
次の Java コードを実行します。
boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1.doubleValue() : d2;
NullPointerException があるのはなぜですか?
条件式の戻り値の型b ? d1.doubleValue : d2
は ですdouble
。条件式には単一の戻り型が必要です。バイナリ数値昇格の規則に従って、d2
は に自動アンボックスされ、 whenがdouble
発生します。NullPointerException
d2 == null
言語仕様のセクション §15.25 から:
それ以外の場合、2 番目と 3 番目のオペランドが数値型に変換可能な型 (§5.1.8) を持っている場合、いくつかのケースがあります: ...
それ以外の場合、2 進数値昇格 (§5.6.2) がオペランドの型に適用され、条件式の型は 2 番目と 3 番目のオペランドの昇格された型になります。バイナリ数値プロモーションでは、ボックス化解除の変換 (§5.1.8) と値セットの変換 (§5.1.13) が実行されることに注意してください。
前後の 2 つの式:
は同じ型を返す必要があるためです。これは、Java が式d2
をに変換しようとすることを意味しますdouble
。これは、バイトコードが -> NPE で呼び出すことを意味しdoubleValue()
ますd2
。
通常、混合型の計算は避けるべきです。これを?:
条件付き/三項と組み合わせると、さらに悪化します。
以下は、 Java Puzzlersの Puzzle 8: Dos Equisからの引用です。
混合型の計算は混乱を招く可能性があります。これは、条件式ほど明白です。[...]
条件式の結果の型を決定するための規則は長すぎて複雑すぎて、すべてを再現することはできませんが、ここでは 3 つのキー ポイントを示します。
2 番目と 3 番目のオペランドが同じ型の場合、それが条件式の型です。言い換えれば、混合型の計算を避けることで、全体の混乱を避けることができます。
オペランドの 1 つが型T であり、 Tが、、またはであり、もう 1 つのオペランドが型Tで表現可能な値を持つ型の定数式である場合、条件式の型はTです。
byte
short
char
int
それ以外の場合、2 進数値昇格がオペランドの型に適用され、条件式の型は 2 番目と 3 番目のオペランドの昇格された型になります。
ここでポイント 3 が適用され、ボックス化解除に至りました。を箱から出すnull
と、当然 aNullPointerException
が投げられます。
混合型の計算の別の例を次に示しますが、?:
これは驚くべきことかもしれません。
Number n = true ? Integer.valueOf(1) : Double.valueOf(2);
System.out.println(n); // "1.0"
System.out.println(n instanceof Integer); // "false"
System.out.println(n instanceof Double); // "true"
混合型の計算は、少なくとも 3 つのJava Puzzlersの対象です。
最後に、Java Puzzlersの規定は次のとおりです。
4.1. 混合型の計算は混乱を招く
処方箋: 混合型の計算を避ける。
数値オペランドで演算子を使用する場合
?:
は、2 番目と 3 番目のオペランドの両方に同じ数値型を使用します。
以下は、Effective Java 2nd Edition、Item 49 からの引用です:ボックス化されたプリミティブよりもプリミティブ型を優先します。
要約すると、選択肢がある場合は常に、ボックス化されたプリミティブよりも優先してプリミティブを使用してください。プリミティブ型はよりシンプルで高速です。ボックス化されたプリミティブを使用する必要がある場合は、注意してください。オートボクシングは冗長性を軽減しますが、ボックス化されたプリミティブを使用する危険性は軽減しません。プログラムが 2 つのボックス化されたプリミティブを
==
演算子と比較する場合、同一性比較が行われますが、これはほとんどの場合、必要なものではありません。プログラムがボックス化されたプリミティブとボックス化されていないプリミティブを含む混合型の計算を行う場合、プログラムはボックス化解除を行い、プログラムがボックス化解除を行う場合は、 をスローできNullPointerException
ます。最後に、プログラムがプリミティブ値をボックス化すると、コストがかかり不要なオブジェクトが作成される可能性があります。
ボックス化されたプリミティブ (ジェネリックなど) を使用せざるを得ない場所もありますが、それ以外の場合は、ボックス化されたプリミティブを使用する決定が正当化されるかどうかを真剣に検討する必要があります。