Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullPointerException using Long after equality check

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?

like image 460
Rob Challen Avatar asked Oct 02 '12 08:10

Rob Challen


People also ask

What is the most likely cause of NullPointerException?

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.

How do I stop NullPointerException in equals method?

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.

Is NullPointerException checked or unchecked?

Java NullPointerException (NPE) is an unchecked exception and extends RuntimeException . NullPointerException doesn't force us to use a try-catch block to handle it.

What can help us in avoiding NullPointerException?

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.


3 Answers

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 type Long, then unboxing conversion converts r into r.longValue()

After unboxing is applied, the rest is standard Java.

like image 173
Tomasz Nurkiewicz Avatar answered Nov 15 '22 11:11

Tomasz Nurkiewicz


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.

like image 45
Peter Lawrey Avatar answered Nov 15 '22 13:11

Peter Lawrey


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:

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

like image 45
verdesmarald Avatar answered Nov 15 '22 13:11

verdesmarald