When I write my java code like this:
Map<String, Long> map = new HashMap<>()
Long number =null;
if(map == null)
number = (long) 0;
else
number = map.get("non-existent key");
the app runs as expected but when I do this:
Map<String, Long> map = new HashMap<>();
Long number= (map == null) ? (long)0 : map.get("non-existent key");
I get a NullPointerException on the second line. The debug pointer jumps from the second line to this method in the java.lang.Thread class:
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
What is happening here? Both these code paths are exactly equivalent isn't it?
Edit
I am using Java 1.7 U25
They are not equivalent.
The type of this expression
(map == null) ? (long)0 : map.get("non-existent key");
is long
because the true result has type long
.
The reason this expression is of type long
is from section §15.25 of the JLS:
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
.
When you lookup a non-existant key the map
returns null
. So, Java is attempting to unbox it to a long
. But it's null
. So it can't and you get a NullPointerException
. You can fix this by saying:
Long number = (map == null) ? (Long)0L : map.get("non-existent key");
and then you'll be okay.
However, here,
if(map == null)
number = (long) 0;
else
number = map.get("non-existent key");
since number
is declared as Long
, that unboxing to a long
never occurs.
What is happening here? Both these code paths are exactly equivalent isn't it?
They are not equivalent; the ternary operator has a few caveats.
The if-true argument of the ternary operator, (long) 0
, is of the primitive type long
. Consequently, the if-false argument will be automatically unboxed from Long
to long
(as per JLS §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
.
However, this argument is null
(since your map does not contain the string "non-existent key"
, meaning get()
returns null
), so a NullPointerException
occurs during the unboxing process.
I commented above suggesting that he ensure map
never be null
, but that does not help with the ternary problem. As a practical matter, it's easier to let the system do the work for you. He could use Apache Commons Collections 4 and its DefaultedMap class.
import static org.apache.commons.collections4.map.DefaultedMap.defaultedMap;
Map<String, Long> map = ...; // Ensure not null.
Map<String, Long> dMap = defaultedMap(map, 0L);
Google Guava doesn't have anything as easy as that, but one can wrap map
with the Maps.transformValues()
method.