Why comparing Integer with int can throw NullPointerException in Java?
Asked Answered
E

7

87

It was very confusing to me to observe this situation:

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
   ...
}

So, as I think boxing operation is executed first (i.e. java tries to extract int value from null) and comparison operation has lower priority that's why the exception is thrown.

The question is: why is it implemented in this way in Java? Why boxing has higher priority then comparing references? Or why didn't they implemented verification against null before boxing?

At the moment it looks inconsistent when NullPointerException is thrown with wrapped primitives and is not thrown with true object types.

Eringo answered 28/7, 2010 at 12:26 Comment(3)
You would get a NullPointerException if you did str.equals("0").Credit
The == operator once used to be save against NPEs under any circumstances. For me this is just another example that demonstrates what a bad idea it was to introduce auto boxing in Java. It just doesn't fit in for so many reasons and offers nothing that hasn't been there before. It only makes the code shorter code while it obscures what is really going on.Lustre
My thoughts are 180 degrees different. They should not have included the primitives used objects everywhere. Then let the compiler optimize and use primitives. Then there would not be any confusion.Burglar
A
144

The Short Answer

The key point is this:

  • == between two reference types is always reference comparison
    • More often than not, e.g. with Integer and String, you'd want to use equals instead
  • == between a reference type and a numeric primitive type is always numeric comparison
    • The reference type will be subjected to unboxing conversion
    • Unboxing null always throws NullPointerException
  • While Java has many special treatments for String, it is in fact NOT a primitive type

The above statements hold for any given valid Java code. With this understanding, there is no inconsistency whatsoever in the snippet you presented.


The Long Answer

Here are the relevant JLS sections:

JLS 15.21.3 Reference Equality Operators == and !=

If the operands of an equality operator are both of either reference type or the null type, then the operation is object equality.

This explains the following:

Integer i = null;
String str = null;

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

Both operands are reference types, and that's why the == is reference equality comparison.

This also explains the following:

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

For == to be numerical equality, at least one of the operand must be a numeric type:

JLS 15.21.1 Numerical Equality Operators == and !=

If the operands of an equality operator are both of numeric type, or one is of numeric type and the other is convertible to numeric type, binary numeric promotion is performed on the operands. If the promoted type of the operands is int or long, then an integer equality test is performed; if the promoted type is float or double`, then a floating-point equality test is performed.

Note that binary numeric promotion performs value set conversion and unboxing conversion.

This explains:

Integer i = null;

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

Here's an excerpt from Effective Java 2nd Edition, Item 49: Prefer primitives to boxed primitives:

In summary, use primitives in preference to boxed primitive whenever you have the choice. Primitive types are simpler and faster. If you must use boxed primitives, be careful! Autoboxing reduces the verbosity, but not the danger, of using boxed primitives. When your program compares two boxed primitives with the == operator, it does an identity comparison, which is almost certainly not what you want. When your program does mixed-type computations involving boxed and unboxed primitives, it does unboxing, and when your program does unboxing, it can throw NullPointerException. Finally, when your program boxes primitive values, it can result in costly and unnecessary object creations.

There are places where you have no choice but to use boxed primitives, e.g. generics, but otherwise you should seriously consider if a decision to use boxed primitives is justified.

References

Related questions

Related questions

Ali answered 28/7, 2010 at 12:28 Comment(2)
As to the why someRef == 0 is always numeric comparison, it's a very sound choice since comparing the references of two boxed primitives is almost always a programmer error. It would be useless to default to reference comparisons in this case.Neiman
Why wouldn't the compiler replace the expression (myInteger == 0) with (myInteger != null && myInteger == 0) instead of relying on the developer to write this boilerplate null-checking code? IMO I should be able to check if (myBoolean) and that should evaluate to true if and only if the underlying value is specifically true -- I shouldn't have to null-check first.Furtherance
I
16

Your NPE example is equivalent to this code, thanks to autoboxing:

if ( i.intValue( ) == 0 )

Hence NPE if i is null.

Illimitable answered 28/7, 2010 at 12:30 Comment(0)
C
4
if (i == 0) {  //NullPointerException
   ...
}

i is an Integer and the 0 is an int so in the what really is done is something like this

i.intValue() == 0

And this cause the nullPointer because the i is null. For String we do not have this operation, thats why is no exception there.

Contrapuntal answered 28/7, 2010 at 12:33 Comment(0)
E
4

The makers of Java could have defined the == operator to directly act upon operands of different types, in which case given Integer I; int i; the comparison I==i; could ask the question "Does I hold a reference to an Integer whose value is i?"--a question which could be answered without difficulty even when I is null. Unfortunately, Java does not directly check whether operands of different types are equal; instead, it checks whether the language allows the type of either operand to be converted to the type of the other and--if it does--compares the converted operand to the non-converted one. Such behavior means that for variables x, y, and z with some combinations of types, it's possible to have x==y and y==z but x!=z [e.g. x=16777216f y=16777216 z=16777217]. It also means that the comparison I==i is translated as "Convert I to an int and, if that doesn't throw an exception, compare it to i."

Eastwood answered 26/11, 2013 at 20:41 Comment(2)
+1: For actually trying to answer the OP's question of "Why is that designed like that?"Almeda
@MartijnCourteaux: Many languages seem to define operators only for operands of matching types, and assume that if a T is ever implicitly convertible to U, such implicit conversion should be performed without complaint any time a U could be accepted but a T could not. Were it not for such behavior, a language could define == in such a way that if in all cases where x==y, y==z, and x==z all compile without complaint, the three comparisons will behave as an equivalence relation. Curious that designers push all sorts of fancy language features, but ignore axiomatic compliance.Eastwood
R
1

It's because of Javas autoboxing feature. The compiler detects, that on the right hand side of the comparison you're using a primitive integer and needs to unbox the wrapper Integer value into a primitive int value as well.

Since that's not possible (it's null as you lined out) the NullPointerException is thrown.

Revolution answered 28/7, 2010 at 12:29 Comment(0)
F
1

In i == 0 Java will try to do auto-unboxing and do a numerical comparison (i.e. "is the value stored in the wrapper object referenced by i the same as the value 0?").

Since i is null the unboxing will throw a NullPointerException.

The reasoning goes like this:

The first sentence of JLS § 15.21.1 Numerical Equality Operators == and != reads like this:

If the operands of an equality operator are both of numeric type, or one is of numeric type and the other is convertible (§5.1.8) to numeric type, binary numeric promotion is performed on the operands (§5.6.2).

Clearly i is convertible to a numeric type and 0 is a numeric type, so the binary numeric promotion is performed on the operands.

§ 5.6.2 Binary Numeric Promotion says (among other things):

If any of the operands is of a reference type, unboxing conversion (§5.1.8) is performed.

§ 5.1.8 Unboxing Conversion says (among other things):

If r is null, unboxing conversion throws a NullPointerException

Fives answered 28/7, 2010 at 12:32 Comment(0)
C
0

Simply write a method and call it to avoid NullPointerException.

public static Integer getNotNullIntValue(Integer value)
{
    if(value!=null)
    {
        return value;
    }
    return 0;
}
Coons answered 9/8, 2020 at 10:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.