web-dev-qa-db-ja.com

整数と整数を比較すると、JavaでNullPointerExceptionがスローされるのはなぜですか?

この状況を観察するのは私にとって非常に混乱しました。

Integer i = null;
String str = null;

if (i == null) {   //Nothing happens
   ...                  
}
if (str == null) { //Nothing happens

}

if (i == 0) {  //NullPointerException
   ...
}
if (str == "0") { //Nothing happens
   ...
}

だから、私はボクシング操作が最初に実行されると思います(つまり、Javaはnullからint値を抽出しようとします)、比較操作は優先順位が低いため、例外がスローされます。

問題は、なぜJavaでこのように実装されているのかということです。参照を比較するよりもボクシングが優先されるのはなぜですか?または、なぜボクシング前にnullに対する検証を実装しなかったのですか?

現時点では、NullPointerExceptionがラップされたプリミティブでスローされ、trueオブジェクトタイプではスローされない場合、一貫性がないように見えます。

74
Roman

短い答え

キーポイントはこれです:

  • _==_は、2つの参照型の間で常に参照比較です。
    • 多くの場合、例えばIntegerStringを使用する場合は、代わりにequalsを使用します
  • 参照型と数値プリミティブ型の間の_==_は、常に数値比較です。
    • 参照型は、ボックス化解除変換の対象となります
    • ボックス化解除nullは常にNullPointerExceptionをスローします
  • JavaにはStringに対する多くの特別な処理がありますが、実際にはプリミティブ型ではありません

上記のステートメントは、指定されたvalidJavaコードに当てはまります。この理解により、提示したスニペットに矛盾はありません。


長い答え

関連するJLSセクションは次のとおりです。

JLS 15.21.3参照平等演算子_==_および_!=_

等価演算子のオペランドが両方とも参照型またはnull型のいずれかである場合、操作はオブジェクトの等価です。

これにより、次のことが説明されます。

_Integer i = null;
String str = null;

if (i == null) {   // Nothing happens
}
if (str == null) { // Nothing happens
}
if (str == "0") {  // Nothing happens
}
_

両方のオペランドは参照型であるため、_==_は参照の等価比較です。

これにより、次のことも説明されます。

_System.out.println(new Integer(0) == new Integer(0)); // "false"
System.out.println("X" == "x".toUpperCase()); // "false"
_

_==_が数値的に等しい場合、少なくとも1つのオペランドは数値型でなければなりません

JLS 15.21.1数値等価演算子_==_および_!=_

等号演算子のオペランドがboth数値型の場合、またはone is数値型のもう一方は数値型に変換可能であり、オペランドに対して2進数の昇格が実行されます。オペランドの昇格されたタイプがintまたはlongの場合、整数の等価性テストが実行されます。昇格された型が_float or_ double`の場合、浮動小数点の等価性テストが実行されます。

バイナリ数値プロモーションは、値セットの変換とボックス化解除の変換を実行することに注意してください。

これは説明します:

_Integer i = null;

if (i == 0) {  //NullPointerException
}
_

Effective Java 2nd Edition、Item 49:プリミティブをボックス化されたプリミティブより優先する)からの抜粋です:

要約すると、選択肢がある場合は、ボックスプリミティブよりもプリミティブを優先して使用してください。プリミティブ型はよりシンプルで高速です。ボックス化されたプリミティブを使用する必要がある場合は、注意してください!オートボクシングは、ボックス化されたプリミティブを使用することによる冗長性を減らしますが、危険性は減らしません。プログラムが2つのボックス化されたプリミティブを_==_演算子と比較すると、IDの比較が行われますが、これはほぼ間違いなく目的の比較ではありません。プログラムがボックス化されたプリミティブとボックス化されていないプリミティブを含む混合型の計算を行う場合、ボックス化解除を行い、プログラムがボックス化解除を行う場合、NullPointerExceptionをスローできます。最後に、プログラムがプリミティブ値をボックス化すると、コストがかかり不必要なオブジェクトが作成される可能性があります。

ボックス化されたプリミティブを使用する以外に選択肢がない場所があります。ジェネリック、ただしそうでない場合は、ボックス化プリミティブを使用する決定が正当化されるかどうかを真剣に検討する必要があります。

参照資料

関連する質問

関連する質問

132

NPEの例はautoboxingのおかげでこのコードと同等です:

if ( i.intValue( ) == 0 )

したがって、inullの場合、NPE。

13
if (i == 0) {  //NullPointerException
   ...
}

私は整数であり、0はintですので、実際に行われることはこのようなものです

i.intValue() == 0

Iがnullであるため、これによりnullPointerが発生します。 Stringにはこの操作はありません。そのため、例外はありません。

Javaのメーカーは==演算子は、さまざまなタイプのオペランドに直接作用します。この場合、Integer I; int i; 比較 I==i;は、「Iは値がIntegerであるiへの参照を保持していますか?」という質問をすることができます。これは、Iはnullです。残念ながら、Javaは、異なるタイプのオペランドが等しいかどうかを直接チェックしません。代わりに、言語がいずれかのオペランドのタイプを他のand-ifのタイプに変換できるかどうかをチェックしますこのような振る舞いは、変数xy、およびzのいくつかの型の組み合わせに対して、可能なことを意味します。持つため x==yおよびy==z だが x!=z [例x = 16777216f y = 16777216 z = 16777217]。また、比較I==iは、「Iをintに変換し、それが例外をスローしない場合、iと比較します」と翻訳されます。

3
supercat

i == 0 Javaは自動ボックス化解除と数値比較を試行します(つまり、iによって参照されるラッパーオブジェクトに格納されている値は値0? ")。

inullであるため、ボックス化解除はNullPointerExceptionをスローします。

推論は次のようになります。

JLS§15.21.1数値等価演算子==および!= の最初の文は次のようになります。

等号演算子のオペランドが両方とも数値型であるか、一方が数値型で、もう一方が数値型に変換可能(§5.1.8)である場合、オペランドに対してバイナリ数値プロモーションが実行されます(§5.6.2)。

明らかにiは数値型に変換可能であり、0は数値型であるため、オペランドに対してバイナリ数値の昇格が実行されます。

§5.6.2 Binary Numeric Promotion は(とりわけ)言っています:

オペランドのいずれかが参照型の場合、ボックス化解除変換(§5.1.8)が実行されます。

§5.1.8 Unboxing Conversion は(とりわけ)言います:

rがnullの場合、ボックス化解除変換はNullPointerExceptionをスローします

1
Joachim Sauer

Java オートボクシング機能のためです。コンパイラは、比較の右側でプリミティブ整数を使用していることを検出し、ラッパー整数値をプリミティブint値に展開する必要があります。

それが不可能であるため(列に並べるとヌルです)、NullPointerExceptionがスローされます。

1
perdian