17

In the following class, the return type of the two methods is inconsistent with the idea that the ternary operator:

return condition?a:b;

is equivalent to

if(condition) {
    return a;
} else{ 
    return b;
}

The first returns a Double and the second a Long:

public class IfTest {
    public static Long longValue = 1l;
    public static Double doubleValue = null;

    public static void main(String[] args) {
        System.out.println(getWithIf().getClass());// outpus Long
        System.out.println(getWithQuestionMark().getClass());// outputs Double
    }

    public static Object getWithQuestionMark() {
        return doubleValue == null ? longValue : doubleValue;
    }

    public static Object getWithIf() {
        if (doubleValue == null) {
            return longValue;
         } else {
            return doubleValue;
        }
    }
}

I can imagine this has to do with the compiler narrow casting the return type of getWithQuestionMark() but is that language wise ok? It's certainly not what I would have expected.

Any insights most welcome!

Edit: there's very good answers below. Additionally, the following question referenced by @sakthisundar explores another side effect of the type promotion occurring in the ternary operator: Tricky ternary operator in Java - autoboxing

4

2 回答 2

14

Basically it's following the rules of section 15.25 of the JLS, specifically:

Otherwise, if the second and third operands have types that are convertible (§5.1.8) to numeric types, then there are several cases:

  • [...]

  • Otherwise, binary numeric promotion (§5.6.2) is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands.

So section 5.6.2 is followed, which will basically involves unboxing - so this makes your expression work as if longValue and doubleValue were of types long and double respectively, and the widening promotion is applied to the long to get an overall result type of double.

That double is then boxed in order to return an Object from the method.

于 2012-08-24T12:03:19.540 回答
3

In addition to @Jon's answer, looking at the bytecode you see:

public static java.lang.Object getWithQuestionMark();
  Code:
   0:   getstatic       #7; //Field doubleValue:Ljava/lang/Double;
   3:   ifnonnull       16
   6:   getstatic       #8; //Field longValue:Ljava/lang/Long;
   9:   invokevirtual   #9; //Method java/lang/Long.longValue:()J
   12:  l2d
   13:  goto    22
   16:  getstatic       #7; //Field doubleValue:Ljava/lang/Double;
   19:  invokevirtual   #10; //Method java/lang/Double.doubleValue:()D
   22:  invokestatic    #11; //Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
   25:  astore_0
   26:  aload_0
   27:  areturn

Whereas if you tell the compiler that you're not interested in numbers:

public static Object getWithQuestionMark() {
    return doubleValue == null ? (Object)longValue : (Object)doubleValue;
}

you'll get what you were after (bytecode)

public static java.lang.Object getWithQuestionMark();
  Code:
   0:   getstatic       #7; //Field doubleValue:Ljava/lang/Double;
   3:   ifnonnull       12
   6:   getstatic       #8; //Field longValue:Ljava/lang/Long;
   9:   goto    15
   12:  getstatic       #7; //Field doubleValue:Ljava/lang/Double;
   15:  areturn

outputs:

$ java IfTest
class java.lang.Long
class java.lang.Long
于 2012-08-24T12:10:57.787 回答