Rob Challen
Rob Challen

Reputation: 63

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?

Upvotes: 6

Views: 14057

Answers (4)

verdesmarald
verdesmarald

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:

  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.

Upvotes: 2

Tomasz Nurkiewicz
Tomasz Nurkiewicz

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

After unboxing is applied, the rest is standard Java.

Upvotes: 4

Brian Agnew
Brian Agnew

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

Peter Lawrey
Peter Lawrey

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

Related Questions