One of my students is getting a null pointer exception when using a ternary operator which sometimes results in null. I think I understand the issue, but it seems like it's resulting from an inconsistent type inference. Or to put another way, I feel like the semantics here are inconsistent and the error should be avoidable without changing his approach.
This question is similar to, but different from Another question about ternary operators. In that question, the null Integer must be forced to an int because the return value of the function is an int. However, that is not the case in my student's code.
This code runs fine:
Integer x = (5>7) ? 3 : null;
The value of x is null. No NPE. In this case, the compiler can figure out that the result of the ternary operator needs to be Integer, so it casts the 3 (an int) to an Integer rather than casting null to int.
However, running this code:
Integer x = (5>7) ? 3 : (5 > 8) ? 4 : null;
results in an NPE. The only reason that would happen is because the null gets cast to an int, but that's not really necessary and seems inconsistent with the first bit of code. That is, if the compiler can deduce for the first snipet that the result of the ternary operator is an Integer, why can't it do that in the second case? The result of the second ternary expression must be an Integer, and since that result is the second result for the first ternary operator, the result of the first ternary operator should also be an Integer.
Another snipet works fine:
Integer three = 3;
Integer x = (5>7) ? three : (5 > 8) ? three+1 : null;
Here, the compiler seems to be able to deduce that the result of both ternary operators is an Integer and so doesn't force-cast the null to int.
The key to this is that the conditional operator is right associative. The rules for determining the result type of a conditional expression are hideously complicated but it boils down to this:
(5 > 8) ? 4 : null
is evaluated, the 2nd operand is an int
, the 3rd is null
, if we look it up in the table, the result type of this expression is Integer
. (In other words: because one of the operands is null
, this is treated as a reference conditional expression)
(5>7) ? 3 : <previous result>
to evaluate, which means that in the table linked above, we need to lookup the result type for 2nd operand int
and 3rd operand Integer
: and it's int
. This means that <previous result>
needs to be unboxed and fails with an NPE
.So why does the first case work then?
There we've got (5>7) ? 3 : null;
, as we've seen if 2nd operand is int
and 3rd is null
, the result type is Integer
. But we assign it to a variable of Integer
type so no unboxing is required.
However this only happens with the null
literal, the following code will still throw an NPE, because operand types int
and Integer
result in a numeric conditional expression:
Integer i = null;
Integer x = (5>7) ? 3 : i;
To sum it up: there is a kind of logic to it but it isn't human logic.
Integer
, the result is an Integer
.int
and the other is Integer
, the result is an int
.null
reference), the result is an Integer
.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With