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


81

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

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がからint値を抽出しようとするnull)、比較操作の優先度が低いと思うので、例外がスローされます。

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

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


str.equals( "0")を実行すると、NullPointerExceptionが発生します。
Ash Burlaczenko 2010

==演算子は、かつてはどのような状況でもNPEに対して保存されていました。私にとってこれは、Javaで自動ボクシングを導入することがいかに悪い考えであったかを示すもう1つの例です。それは非常に多くの理由で適合せず、これまでになかったものは何も提供しません。実際に何が起こっているのかがわかりにくくなる一方で、コードが短くなるだけです。
x4u 2010

私の考えは180度異なります。どこにでもオブジェクトを使用するプリミティブを含めるべきではありませんでした。次に、コンパイラーにプリミティブを最適化して使用させます。そうすれば、混乱はありません。
MrJacqes 2010

回答:


138

短い答え

重要な点はこれです:

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

上記のステートメントは、任意の有効なJavaコードに当てはまります。この理解により、提示したスニペットに矛盾はまったくありません。


長い答え

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

JLS15.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つが数値型である必要があります

JLS15.21.1数値等式演算子==!=

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

2進数の昇格は、値セット変換とボックス化解除変換を実行することに注意してください。

これは説明します:

Integer i = null;

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

これは、Effective Java 2nd Editionのアイテム49からの抜粋です:ボックス化されたプリミティブよりもプリミティブを優先します:

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

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

参考文献

関連する質問

関連する質問


2
なぜ someRef == 0常に数値比較であるかについては、2つのボックス化されたプリミティブの参照を比較することはほとんどの場合プログラマーのエラーであるため、これは非常に適切な選択です。この場合、デフォルトで比較を参照することは無意味です。
マークピーターズ

3
この定型的なヌルチェックコードの記述を開発者に頼るのではなく、コンパイラが式(myInteger == 0)(myInteger != null && myInteger == 0)に置き換えないのはなぜですか?IMOチェックできるはずであり、基礎となる値が具体的にある場合にのみif (myBoolean)評価されるはずです。最初にnullチェックする必要はありません。truetrue
ジョシュ・M.


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

iは整数で、0は整数なので、実際に行われるのは次のようなものです。

i.intValue() == 0

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


4

ジャワのメーカーが定義されている可能性が==直接与えられ、その場合には、異なる種類のオペランドに作用するオペレータInteger I; int i;の比較はI==i;「んは、質問をすることができIへの参照を保持Integer値があるがi問題なく答えられる可能性が疑問を- ?」Iがnullの場合でも。残念ながら、Javaは異なるタイプのオペランドが等しいかどうかを直接チェックしません。代わりに、言語がいずれかのオペランドのタイプを他方のタイプに変換できるかどうかをチェックし、許可している場合は、変換されたオペランドを変換されていないオペランドと比較します。変数のために、このような行動手段xyおよびzタイプのいくつかの組み合わせで、それを持つことが可能だx==yy==zけどx!=z[例:x = 16777216f y = 16777216 z = 16777217]。また、比較I==iが「Iをに変換intし、それでも例外がスローされない場合は、に比較する」と変換されることも意味しiます。


+1:「なぜそれがそのように設計されているのか」というOPの質問に実際に答えようとしたことに対して。
Martijn Courteaux 2013年

1
@MartijnCourteaux:多くの言語は、一致する型のオペランドに対してのみ演算子を定義しているようであり、Tが暗黙的にUに変換可能である場合、そのような暗黙の変換は、Uが受け入れられるが、Tが受け入れられないときはいつでも文句なしに実行する必要があると想定しています。そのような振る舞い==がなければ、言語は、、、およびすべてが文句なしにコンパイルされるすべての場合にx==y、3つの比較が同値関係として振る舞うように定義することができます。デザイナーがあらゆる種類の派手な言語機能をプッシュするが、公理的コンプライアンスを無視することに興味があります。y==zx==z
スーパーキャット2013年

1

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

それは不可能なので(あなたが並べたようにそれはnullです)、NullPointerExceptionがスローされます。


1

ではi == 0Javaの自動アンボクシングをやろうと数値の比較(すなわち「を行うだろうが参照するラッパーオブジェクトに格納された値であるi値と同じ0?」)。

以来iであるnullアンボクシングがスローされます。NullPointerException

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

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

等式演算子のオペランドが両方とも数値型である場合、または一方が数値型であり、もう一方が数値型に変換可能(§5.1.8)である場合、2進数値の昇格はオペランド(§5.6.2)で実行されます。

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

§5.6.2バイナリ数値プロモーションは(とりわけ)次のように述べています。

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

§5.1.8ボックス化解除の変換によると(とりわけ):

rがnullの場合、ボックス化解除変換はNullPointerException


0

NullPointerExceptionを回避するには、メソッドを記述して呼び出すだけです。

public static Integer getNotNullIntValue(Integer value)
{
    if(value!=null)
    {
        return value;
    }
    return 0;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.