Java conditional operator ?: result type

13,472

Solution 1

The difference is static typing of the expressions at compile time:

Summary

E1: `(false ? 1.0f : null)`
    - arg 2 '1.0f'           : type float,
    - arg 3 'null'           : type null 
    - therefore operator ?:  : type Float (see explanation below)
    - therefore autobox arg2
    - therefore autobox arg3

E2: `(false ? 1.0f : (false ? 1.0f : null))`
    - arg 2 '1.0f'                    : type float
    - arg 3 '(false ? 1.0f : null)'   : type Float (this expr is same as E1)
    - therefore, outer operator ?:    : type float (see explanation below)
    - therefore un-autobox arg3

Detailed Explanation:

Here's my understand from reading through the spec and working backwards from the result you got. It comes down to the type of the third operand of the f2 inner conditional is null type while the type of the third operand of the f2 outer conditional is deemed to be Float.

Note: Its important to remember that the determination of type and the insertion of boxing/unboxing code is done at compile-time. Actual execution of boxing/unboxing code is done at run-time.

Float f1 = (false ? 1.0f : null);
Float f2 = (false ? 1.0f : (false ? 1.0f : null));

The f1 conditional and the f2 inner conditional: (false ? 1.0f : null)

The f1 conditional and the f2 inner conditional are identical: (false ? 1.0f : null). The operand types in the f1 conditional and the f2 inner conditional are:

type of second operand = float
type of third operand = null type (§4.1)

Most of the rules in §15.25 are passed up and this final evaluation is indeed applied:

Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2. The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7).

S1 = float
S2 = null type
T1 = Float
T2 = null type
type of the f1 and f2 inner conditional expressions = Float

Since for f1, the assignment is to a Float reference variable, the result of the expression (null) is successfully assigned.

For f2 outer conditional: (false ? 1.0f : [f2 inner conditional])

For the f2 outer conditional, the types are:

type of second operand = float
type of third operand = Float

Note the difference in operand types compared to the f1/f2 inner conditionals that reference the null literal directly (§4.1). Because of this difference of having 2 numeric-convertible types, this rule from §15.12.2.7 applies:

  • 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. Note that binary numeric promotion performs unboxing conversion (§5.1.8) and value set conversion (§5.1.13).

Because of the unboxing conversion performed on the result of the f2 inner conditional (null), a NullPointerException is raised.

Solution 2

I think rewriting the code makes the explanation clearer:

    float f = 1.0f;

    Float null_Float  = false?        f  : null;       // float + null  -> OK
    Float null_Float2 = false? (Float)f  : null_Float; // Float + Float -> OK
    Float npe         = false?        f  : null_Float; // float + Float -> NPE

Thus the NPE is when we try to do something like:

Float npe = false? 1.0f : (Float)null;

Solution 3

The following will throw a NPE as you attempt assign a null to a primitive

    float f1 = false ? 1.0f: null;

That I believe is whats causing the NPE in the second statement. Because the first ternary returns a float for true it attempts to convert the false to a float as well.

The first statement will not convert to null as the required result is a Float

This for example this would not throw a NPE as its no longer needs to convert to primitive

    Float f = false? new Float(1.0f): true ? null : 1.0f;

Solution 4

To be or not to be, that is the question. :)

Edit: Actually, looking closer it seems that this case is actually a mix between the Hamlet (ternary operator and wrapped integral types) and the Elvis (auto-unboxing null) puzzlers. In any case, I can only recommend watching the video, it is very educational and enjoyable.

Solution 5

It looks like the JVM tries to unbox the second null to float instead of Float, thus NullPointerException. Hit it myself once. My take is that the second if does it because the true part of the first if evaluates as a float, not a Float.

After giving it a second thought, I think this a way of Java telling you that you are doing something strange. Just don't nest ternary ifs and you will be fine :-)

Share:
13,472
Wangnick
Author by

Wangnick

Updated on June 13, 2022

Comments

  • Wangnick
    Wangnick about 2 years

    I'm a bit puzzled about the conditional operator. Consider the following two lines:

    Float f1 = false? 1.0f: null;
    Float f2 = false? 1.0f: false? 1.0f: null;
    

    Why does f1 become null and the second statement throws a NullPointerException?

    Langspec-3.0 para 15.25 sais:

    Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2. The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7).

    So for false?1.0f:null T1 is Float and T2 is the null type. But what is the result of lub(T1,T2)? This para 15.12.2.7 is just a bit too much ...

    BTW, I'm using 1.6.0_18 on Windows.

    PS: I know that Float f2 = false? (Float) 1.0f: false? (Float) 1.0f: null; doesn't throw NPE.

  • Wangnick
    Wangnick about 14 years
    Well, according to my understanding the type of the ?: result is established upfront and independantly of the evaluation of the condition. So what is the type of false?1.0f:null, which is used as the third value in F2? If it was the null type, then F2 should not NPE. If it was float, neither, nor if it was Float.
  • Leif Andersen
    Leif Andersen about 14 years
    If that was the case, than why isn't he getting a compiler error, and is being allowed to see this at run time?
  • Wangnick
    Wangnick about 14 years
    The ?: result type conversion should be completely independant of the later assignment, no? Note that the same issue occurs when just printing the expressions: System.out.println(false? 1.0f: null); -> null System.out.println(false? 1.0f: false? 1.0f: null); -> NPE I know how to work around this particular case but I'd really like to understand whats going on, so as to avoid NPE's in my application code.
  • Wangnick
    Wangnick about 14 years
    If false?1.0f:null of the f1 case works fine resulting in null, why does it not work fine as the third value in the f2 expression?
  • objects
    objects about 14 years
    Because it converts it to a float due to the true case of first ternary being a float
  • Wangnick
    Wangnick about 14 years
    Bert, I tried System.out.println((false?1.0f:null) instanceof Float) which printed false, but this is clear when reading 15.20.2. Is there any means in Java to retrieve the type of an expression? Whats also funny is that when you Inspect the expression false?1.0f:false?1.0f:null, Eclipse tells you that its value is null.
  • Bert F
    Bert F about 14 years
    That is a nifty link: "Advanced Topics in Programming Languages: Java Puzzlers" by GoogleTechTalks. It even advances to specific point in the video. I learn something new every day.
  • Slava Imeshev
    Slava Imeshev about 14 years
    Yes, the type is established upfront, but we are talking about nulls, and the system has to deduce type. The second if type is float, not Float.
  • Wangnick
    Wangnick about 14 years
    Very nice and educating puzzles in that link indeed!
  • Bert F
    Bert F about 14 years
    @Wangnick - change to 'System.out.println((true?1.0f:null) instanceof Float)' - this returns 'true'. You'll just have to accept that the compile-time type is the same as when you say '(false?1.0f:null)'. BUT if you really want to convince yourself - look at the bytecode. Change code to 'boolean b = false; System.out.println((b?1.0f:null) instanceof Float);' or compiler will evaluate the §15.28 constant expression '(false?1f:null)' at compile-time (leaving just 'aconst_null' in bytecode). But the new code should have bytecode that casts '1f' (operand 2) to Float - therefore (?:) is float.
  • Bert F
    Bert F about 14 years
    @Wangnick - Err... that last 'float' above should be 'Float' (dang editor timer). If you are examining the bytecode (I'm using Eclipse), you can also try removing the 'instanceof' and changing the 'null' to '2.0f' and see the bytecode Float cast disappear - '?:' expr is now float. Change '2.0f' back to 'null' and Float cast is back. In Eclipse, don't forget to close the .class editor window each time or you might not see the bytecode changes.