6
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);
    }
}
Lightyear Buzz
  • 796
  • 7
  • 28
  • 2
    Reproduced here. This is weird. Yes, the OP should be using `equals()`, but it's always interesting to play with these things. – Mysticial Feb 06 '13 at 02:23
  • *missing brackets. And yes, sometimes true, others false. Except that null is sometimes 0(or a bunch of zeros), wich is a number. – Afonso Tsukamoto Feb 06 '13 at 02:23
  • @Mysticial If you like weird (and undecided yet) stuff: http://stackoverflow.com/questions/14624365/immutability-and-reordering – assylias Feb 06 '13 at 02:43

2 Answers2

8

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.

assylias
  • 321,522
  • 82
  • 660
  • 783
  • So you're saying that constant-propagation is followed by auto-boxing into a new object. So they should both return false. But then integers are cached for small values, so that returns true? – Mysticial Feb 06 '13 at 02:26
  • @Mysticial Basically if you use boxing on char, byte or int in the -127/+128 range (e.g `Integer i = 123;`), you are guaranteed to always receive the same instance. It is not required for Longs but seems to be the case on my JVM (Oracle JDK 7u11). So no I am not saying that the behaviour is due to Integers being cached. – assylias Feb 06 '13 at 02:28
  • Yeah, the caching part I know since I've seen before on SO. When I looked at the question, I actually expected both to return true since they would both be references to the original object. But here I guess that's not the case. The compiler/JVM is indeed making new objects (why?). – Mysticial Feb 06 '13 at 02:32
  • @Mysticial Ah ok I see what you are saying. Probably due to the ternary operator doing some tricks. – assylias Feb 06 '13 at 02:34
  • Yeah, removing the ternary operator and just returning `DEFAULT_DOUBLE` and `DEFAULT_LONG` directly makes both return true. Removing the `final` doesn't do anything. Still `false/true`. – Mysticial Feb 06 '13 at 02:36
  • @assylias - Yes, it's the ternary operator. See my Answer – Stephen C Feb 06 '13 at 02:53
  • @StephenC Yes I had edited similarly, although not with so many details! +1 for you ;-) – assylias Feb 06 '13 at 03:00
4

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;
    
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216