Run the following Java code:
boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1.doubleValue() : d2;
Why is there a NullPointerException?
Run the following Java code:
boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1.doubleValue() : d2;
Why is there a NullPointerException?
Because the two expressions around :
must return the same type. This means Java tries to convert the expression d2
to double
. This means the bytecode calls doubleValue()
on d2
-> NPE.
The return type of the conditional expression b ? d1.doubleValue : d2
is double
. A conditional expression must have a single return type. Following the rules for binary numeric promotion, d2
is autounboxed to a double
, which causes a NullPointerException
when d2 == null
.
From the language spec, section §15.25:
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).
You should generally avoid mixed type computation; compounding this with ?:
conditional/ternary only makes it worse.
Here's a quote from Java Puzzlers, Puzzle 8: Dos Equis:
Mixed-type computation can be confusing. Nowhere is this more apparent than conditional expression. [...]
The rules for determining the result type of a conditional expression are too long and complex to reproduce in their entirety, but here are three key points.
If the second and third operands have the same type, that is the type of the conditional expression. In other words, you can avoid the whole mess by steering clear of mixed-type computation.
If one of the operands is of type T where T is
byte
,short
, orchar
, and the other operand is a constant expression of typeint
whose value is representible in type T, the type of the conditional expression is T.Otherwise, binary numeric promotion is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands.
Point 3 is applied here, and it resulted in unboxing. When you unbox null
, naturally a NullPointerException
is thrown.
Here's another example of mixed-type computation and ?:
that may be surprising:
Number n = true ? Integer.valueOf(1) : Double.valueOf(2);
System.out.println(n); // "1.0"
System.out.println(n instanceof Integer); // "false"
System.out.println(n instanceof Double); // "true"
Mixed-type computation is the subject of at least 3 Java Puzzlers.
In closing, here's what Java Puzzlers prescribes:
4.1. Mixed-type computations are confusing
Prescription: Avoid mixed-type computations.
When using the
?:
operator with numeric operands, use the same numeric type for both the second and third operands.
Here's a quote from Effective Java 2nd Edition, Item 49: Prefer primitive types to boxed primitives:
In summary, use primitives in preference to boxed primitive whenever you have the choice. Primitive types are simpler and faster. If you must use boxed primitives, be careful! Autoboxing reduces the verbosity, but not the danger, of using boxed primitives. When your program compares two boxed primitives with the
==
operator, it does an identity comparison, which is almost certainly not what you want. When your program does mixed-type computations involving boxed and unboxed primitives, it does unboxing, and when your program does unboxing, it can throwNullPointerException
. Finally, when your program boxes primitive values, it can result in costly and unnecessary object creations.
There are places where you have no choice but to use boxed primitives, e.g. generics, but otherwise you should seriously consider if a decision to use boxed primitives is justified.