NullPointerException through auto-boxing-behavior of Java ternary operator
Asked Answered
B

3

15

I tripped across a really strange NullPointerException the other day caused by an unexpected type-cast in the ternary operator. Given this (useless exemplary) function:

Integer getNumber() {
    return null;
}

I was expecting the following two code segments to be exactly identical after compilation:

Integer number;
if (condition) {
    number = getNumber();
} else {
    number = 0;
}

vs.

Integer number = (condition) ? getNumber() : 0;

.

Turns out, if condition is true, the if-statement works fine, while the ternary opration in the second code segment throws a NullPointerException. It seems as though the ternary operation has decided to type-cast both choices to int before auto-boxing the result back into an Integer!?! In fact, if I explicitly cast the 0 to Integer, the exception goes away. In other words:

Integer number = (condition) ? getNumber() : 0;

is not the same as:

Integer number = (condition) ? getNumber() : (Integer) 0;

.

So, it seems that there is a byte-code difference between the ternary operator and an equivalent if-else-statement (something I didn't expect). Which raises three questions: Why is there a difference? Is this a bug in the ternary implementation or is there a reason for the type cast? Given there is a difference, is the ternary operation more or less performant than an equivalent if-statement (I know, the difference can't be huge, but still)?

Brick answered 6/10, 2012 at 21:21 Comment(7)
You seriously believe that there's a bug in ternary operator rather than in your understanding of the documented use and restrictions of the operator? What do you think are the odds of this realistically occurring? Consider changing the title of your question to "misunderstanding in how the ternary operator works".Forepaw
This is why I'm asking the question why the compiler decides that getNumber() and 0 should both evaluate to an int if I assign the result to an Integer. To me, it makes absolutely no sense to cast the two arguments to the more restrictive of the two types BEFORE the comparison rather than to the actually required type AFTER the comparison. Why go through unboxing and then reboxing getNumber()?Brick
It makes no difference what you or I think should happen. Rather all that matters is what is documented clearly in the JLS.Forepaw
I removed "bug" from the title and tags - please try to write a concise relevant title.Aday
@pst: Good call. I changed the title and tags again slightly to make this thread more easily findable if someone else runs into this issue and tries googling for it. This was a real headache to debug, especially if the "null" case happens only very rarely. Which, is partly why I thought this could be a bug since it makes Java produce less stable code for (to me) no apparent reason.Brick
The reason this was so hard to debug was that my actual code looked like this: Long directoryID = item.isDirectory() ? item.getID() : item.getParentID(); It just so happened that getID returned a long and getParentID returned a Long (null = root directory). There's a billion ways I came up with that could result in a NullPointerException in this line, but the auto-boxing, of course, was not one of them. So, I guess, the real "bug" here is that auto-boxing-errors of this sort raise a NullPointerException rather than a ClassCastException or their own exception entirely.Brick
I'm not completely convinced by explanation below. (I 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.) because the other operand is not the result of applying boxing conversion as specified in (§5.1.7) (rather unboxing conversion if anything, but no conversion should happen there).Filemon
N
15

According to JLS: -

The type of a conditional expression is determined as follows:

  • If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
  • 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.
Nihi answered 6/10, 2012 at 21:31 Comment(3)
So, it's supposed to be this way. Leaves the question of whether there is a performance difference between the two, especially given all the auto-(un)-boxing happening.Brick
@Markus.. Well, you cannot say whether there is a performance difference or not.. This certainly gives the programmer the ease of coding.. But yes, since this include little part of Unboxing.. which is not in case of if-else.. So may be performance will be low..Nihi
@Markus.. But given that ternary operator is mostly used only in case of single statement condition.. So, it is not much of a worry.. Of course you won't be able to move the whole expression from an if-else block to ternary operator.. So, both of them has pros and cons..Nihi
P
11

The problem is that:

Integer number = (condition) ? getNumber() : 0;

Forces an unboxing and reboxing of the result of getNumber(). This is because the false part of the ternary (0) is an integer, so it tries to convert the result of getNumber() to an int. Whereas the following does not:

Integer number = (condition) ? getNumber() : (Integer) 0;

This is not a bug, just the way Java chose to do things.

Preconscious answered 6/10, 2012 at 21:24 Comment(1)
@Markus See the Java language specification 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.Staff
H
2

This is how it is supposed to work. The ternary operator is not meant to be equivalent to a regular if statement. The bodies of if and else are statements, while the parts following ? and : are expressions, that are required to evaluate to the same type.

Put another way: a = b ? c : d is not supposed to be equivalent to if (b) a = c; else a = d;. Instead, b ? c : d is an expression on its own, and the assignment of its result to a won't affect the outcome.

Hobby answered 6/10, 2012 at 21:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.