使用Java三元运算符时的奇怪行为

时间:2013-07-09 13:38:13

标签: java nullpointerexception ternary-operator

当我写这样的java代码时:

Map<String, Long> map = new HashMap<>()
Long number =null;
if(map == null)
    number = (long) 0;
else
    number = map.get("non-existent key");

应用程序按预期运行但是当我这样做时:

Map<String, Long> map = new HashMap<>();
Long number= (map == null) ? (long)0 : map.get("non-existent key");

我在第二行得到NullPointerException。调试指针从第二行跳转到java.lang.Thread类中的this方法:

 /**
     * 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);
     }

这里发生了什么?这两个代码路径完全等价不是吗?


修改

我正在使用Java 1.7 U25

3 个答案:

答案 0 :(得分:15)

它们并不等同。

此表达式的类型

(map == null) ? (long)0 : map.get("non-existent key");

long,因为真实结果的类型为long

此表达式属于long类型的原因来自§15.25 of the JLS部分:

  

如果第二个和第三个操作数之一是基本类型T,而另一个操作数的类型是将装箱转换(第5.1.7节)应用于T的结果,那么类型条件表达式是T

当您查找不存在的密钥时,map会返回null。因此,Java正试图将其解包到long。但它是null。所以它不能,你得到NullPointerException。您可以通过说:

来解决这个问题
Long number = (map == null) ? (Long)0L : map.get("non-existent key");

然后你会没事的。

然而,在这里,

if(map == null)
    number = (long) 0;
else
    number = map.get("non-existent key");

因为number被声明为Long,所以取消装箱到long永远不会发生。

答案 1 :(得分:3)

  

这里发生了什么?这两个代码路径完全等价不是吗?

它们不等同;三元运算符有一些警告。

三元运算符的{if-true参数(long) 0是原始类型 long 。因此,if-false参数将从Long自动取消装箱到long(根据JLS §15.25):

  

如果第二个和第三个操作数之一是基本类型T,而另一个操作数的类型是将装箱转换(§5.1.7)应用于T的结果,那么条件表达式的类型是T

但是,此参数为null(因为您的地图不包含字符串"non-existent key",意味着get()返回null),因此发生了NullPointerException在拆箱过程中。

答案 2 :(得分:0)

我在上面评论过,他建议他确保map永远不会null,但这对三元问题没有帮助。实际上,让系统为您完成工作更容易。他可以使用Apache Commons Collections 4及其DefaultedMap类。

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没有那么简单,但可以使用map方法包装Maps.transformValues()