Consider the following Java code:
Integer foo = bar();
if(foo == 5) ...;
if(5 == foo) ...;
Are these comparisons equal -- particularly in the possibility of foo
being null
? Do they expand to foo.getValue() == 5
and 5 == foo.getValue()
, or to something more akin to foo.equals(new Integer(5))
and new Integer(5).equals(foo)
, or to something else? May one or the other or both or none throw an NPE?
From the 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 (§5.1.8) to numeric type, binary numeric promotion is performed on the operands (§5.6.2).
And the relevant rule from 5.1.8 is:
If r is a reference of type Integer, then unboxing conversion converts r into r.intValue()
And 5.6.2 says:
5.6.2. Binary Numeric Promotion
When an operator applies binary numeric promotion to a pair of operands, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:
If any operand is of a reference type, it is subjected to unboxing conversion (§5.1.8).
Which means that if(foo == 5) ...;
means the same as if(foo.intValue() == 5) ...;
and if(5 == foo)
means if (5 == foo.intValue())
.
If foo
equals null
then you will get an NPE in either case.
==
is symmetric; that is to say, for any values x
and y
, (x == y) == (y == x)
. This is a guarantee provided to us by the JLS §15.21.1 for numbers, and §15.21.3 for reference types (or everything that isn't a primitive value).
It could also be seen as transitive, in that if three values x, y, z
exist, and x == y && y == z
, then x == z
. This is again provided by the same JLS specification - merely repeated to mitigate the issue of the common variable y
.
The real problem here comes with regards to autoboxing; when you go to unbox null
, then by the JLS, you're going to get a NullPointerException
- independent of the comparison operation you're going to do next.
Effectively:
You have a boxed primitive type on one side of the comparison, and a primitive on the other. The value of either isn't yet considered.
Given that the value of the primitive will force numerical comparison due to it being a boxed primitive, Java will then try to unbox the boxed value.
You can't unbox null
, hence NullPointerException
.
This is (kind of) where equals()
steps in - by its contract, two non-null instances must be equivalent to each other if they are indeed the same thing. If either (but not both) of these values are null
, then they're not the same instances.
I say "kind of" since there's really nothing to enforce the supposed contract on Object#equals
; you could (with some effort) write an asymmetric equals()
method, although one would wonder why you would want to.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With