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?
What Causes NullPointerException. The NullPointerException occurs due to a situation in application code where an uninitialized object is attempted to be accessed or modified. Essentially, this means the object reference does not point anywhere and has a null value.
Answer: Some of the best practices to avoid NullPointerException are: Use equals() and equalsIgnoreCase() method with String literal instead of using it on the unknown object that can be null. Use valueOf() instead of toString() ; and both return the same result.
Java NullPointerException (NPE) is an unchecked exception and extends RuntimeException . NullPointerException doesn't force us to use a try-catch block to handle it.
Java 8 introduced an Optional class which is a nicer way to avoid NullPointerExceptions. You can use Optional to encapsulate the potential null values and pass or return it safely without worrying about the exception. Without Optional, when a method signature has return type of certain object.
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.
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.
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.
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