Reputation: 63
This threw me.
If you have a Java Long variable and you check equality against a primitive value using the == operator the runtime type of the value is changed to a primitive long.
Subsequent checking the variable for a null value then throws an unexpected NullPointerException.
So in the test class:
public class LongDebug {
public static void main(String[] args) {
Long validValue = 1L;
Long invalidValue = -1L;
Long nullValue = null;
System.out.println("\nTesting the valid value:");
testExpectedBehaviour(validValue);
testUnExpectedBehaviour(validValue);
System.out.println("\nTesting the invalid value:");
testExpectedBehaviour(invalidValue);
testUnExpectedBehaviour(invalidValue);
System.out.println("\nTesting the null value:");
testExpectedBehaviour(nullValue);
testUnExpectedBehaviour(nullValue);
}
/**
* @param validValue
*/
private static void testExpectedBehaviour(Long value) {
if (value == null || value == -1) System.out.println("Expected: The value was null or invalid");
else System.out.println("Expected: The value was valid");
}
private static void testUnExpectedBehaviour(Long value) {
try {
if (value == -1 || value == null) System.out.println("Unexpected: The value was null or invalid");
else System.out.println("Unexpected: The value was valid");
} catch (NullPointerException e) {
System.out.println("Unexpected: The system threw an unexpected NullPointerException");
}
}
}
The result I get is:
Testing the valid value:
Expected: The value was valid
Unexpected: The value was valid
Testing the invalid value:
Expected: The value was null or invalid
Unexpected: The value was null or invalid
Testing the null value:
Expected: The value was null or invalid
Unexpected: The system threw an unexpected NullPointerException
Is this on spec or a bug in the JDK?
Upvotes: 6
Views: 14057
Reputation: 11866
It's part of the spec, specifically 5.6.2. Binary Numeric Promotion and 5.1.8. Unboxing Conversion. The relevant parts:
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).
[...]
Binary numeric promotion is performed on the operands of certain operators:
[...]
- The numerical equality operators == and != (§15.21.1)
And:
5.1.8. Unboxing Conversion
[...]
- If r is a reference of type Long, then unboxing conversion converts r into r.longValue()
[...]
- If r is null, unboxing conversion throws a NullPointerException
Note that if (value == null || value == -1)
doesn't throw the exception because of short-circuit evaluation. Since value == null
is true
, the second part of the expression value == -1
is never evaluated, so value
is not unboxed in this case.
Upvotes: 2
Reputation: 340883
This is the problem:
value == -1 || value == null
Expressions are evaluated from left to right and since Long
must be unboxed first, JVM translates this to:
value.longValue() == -1 || value == null
And value.longValue()
throws NullPointerException
when value
is null
argument. It never reaches the second part of the expression.
It works when the order is different though:
value == null || value == -1
because if the value
is null
, the second part (that can cause NullPointerException
when value
is null
) is never executed due to boolean expression short-circuit evaluation.
Is this on spec or a bug in the JDK?
Of course this is not a bug. The way primitive value wrappers are unboxed is on spec (5.1.8. Unboxing Conversion):
- If
r
is a reference of typeLong
, then unboxing conversion convertsr
intor.longValue()
After unboxing is applied, the rest is standard Java.
Upvotes: 4
Reputation: 272337
Your issue is the order of the checks for null/primitive value in your two tests. Since you're passing null, this:
if (value == null || value == -1)
won't unbox, since the first check is true. The tests are performed left-to-right. This
if (value == -1 || value == null)
will try to unbox (to compare to -1), however, and fail since you're unboxing a null
value. That behaviour (unboxing a null value throwing an exception) is expected.
Upvotes: 0
Reputation: 533660
Is this on spec or a bug in the JDK?
This is normal. If you dereference a reference which is null
you should get a NullPointerException. This means if you are going to check for null
you have to check it before this happens. Checking it after is pointless and confusing.
if (value == -1 || value == null)
is the same as
if (value.longValue() == -1 || value == null)
and the first part of the expression throws an NPE before the second part is run. If the first part doesn't fail the second part must be false.
Upvotes: 2