Lightyear Buzz
Lightyear Buzz

Reputation: 796

Why does this return false and true?

public class Test {
    public static final Double DEFAULT_DOUBLE = 12.0;
    public static final Long DEFAULT_LONG = 1L;

    public static Double convertToDouble(Object o) {
        return (o instanceof Number) ? ((Number) o).doubleValue()
                : DEFAULT_DOUBLE;
    }

    public static Long convertToLong(Object o) {
        return (o instanceof Number) ? ((Number) o).longValue() : DEFAULT_LONG;
    }

    public static void main(String[] args){
        System.out.println(convertToDouble(null) == DEFAULT_DOUBLE);
        System.out.println(convertToLong(null) == DEFAULT_LONG);
    }
}

Upvotes: 6

Views: 398

Answers (2)

Stephen C
Stephen C

Reputation: 719641

To understand the strange behaviour, you need to pick apart what this does:

(o instanceof Number) ? ((Number) o).longValue() : DEFAULT_LONG

What we have here is a call to method that returns a long, and an instance of Long. Two different types. However the conditional operator ? needs to produce a single type; either long or Long. And in order to do that, either the second operand must be boxed or the third operand must be unboxed.

In this case, the JLS says that the third operand must be unboxed. JLS 15.25

"If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T."

And that means that your constants are being unboxed and then boxed again.

Now boxing a primitive type is done by calling the boxed type's valueof() method. And these methods do slightly different things depending on the base type.

  • For a floating point type, the valueof always creates a new object
  • For an integral type, the valueof method sometimes creates a new object, and sometimes returns an existing object.

And the latter is what is happening here. Long.valueof(1L) is always returning the same value, and the comparison with == is giving true.


There are two "fixes" ... assuming you want to fix this:

  • Replace the ternary operator with an if / else with a return in both branches.
  • Cast the second operand to force it to be boxed:

    return (o instanceof Number) ? 
            (Long) ((Number) o).longValue() : DEFAULT_LONG;
    

Upvotes: 4

assylias
assylias

Reputation: 328913

EDIT

The ternary operator does some type conversions under the hood. In your case you are mixing primitives and wrapper types, in which case the wrapper types gets unboxed, then the result of the ternary operator is "re-boxed":

If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.


So your code is essentially equivalent to (apart from the typo where longValue should be doubleValue):

public static void main(String[] args){
    Double d = 12.0;
    System.out.println(d == DEFAULT_DOUBLE);

    Long l = 1L;
    System.out.println(l == DEFAULT_LONG);
}

Long values can be cached on some JVMs and the == comparison can therefore return true. If you made all the comparisons with equals you would get true in both cases.

Note that if you use public static final Long DEFAULT_LONG = 128L; and try:

Long l = 128L;
System.out.println(l == DEFAULT_LONG);

It will probably print false, because Long values are generally cached between -128 and +127 only.

Note: The JLS requires that char, byte and int values between -127 and +128 be cached but does not say anything about long. So your code might actually print false twice on a different JVM.

Upvotes: 8

Related Questions