简体   繁体   中英

Weird behaviour when using Java ternary operator

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM