使用Java三目运算符时出现奇怪的行为

12

当我像这样编写我的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类中的这个方法:

 /**
     * 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版本。


为什么要测试不可能的情况?你自己创建了map;如果它是null,那么你的运行时系统就出问题了。既然这显然是你正在进行的其他工作的缩短版本,我建议你确保对象始终被初始化,这样你就不必进行map == null检查了。键是你的问题。 - Eric Jablow
@Eric Jablow 这只是我的代码的简化版本。相关的映射实际上是从另一个地方检索到的,它们是根据需求动态创建的。这就是为什么要进行检查。在这里,我包括了初始化过程,以便回答问题的人们清楚地知道,在出现问题时,并不会执行 if-true 情况。 - radiantRazor
我现在明白了。如果有人向您的代码传递一个null映射,最好立即抛出异常。或者,如果有人向您的代码传递一个null,则将您的映射设置为空映射。 - Eric Jablow
1
可能是奇怪的Java自动装箱NullPointerException的重复问题。 - default locale
2
实际上,这里还有一个更古老和更流行的类似问题。 - default locale
3个回答

15

他们不是等价的。

这个表达式的类型

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

由于true的结果类型为long,因此该表达式是long类型的。

这个表达式的类型为long类型,原因在于JLS的第§15.25节:

如果第二个和第三个操作数中的一个是原始类型,而另一个的类型是将应用于装箱转换(§5.1.7)的结果,则条件表达式的类型为。

当查找不存在的键时,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的情况。

在这种情况下,发帖人是否可以通过将0转换为“Long”而不是“long”来使用三元运算符? - ameed
1
是的,他需要将0L强制转换为Long类型。 - jason
2
哇,JLS有很多隐含的陷阱。我显而易见的后续问题是,为什么JLS被定义为将类型推断为基本类型?如果定义为将类型推断为自动装箱类型,则可以避免这种NullPointerException异常。也许我会尝试把这个问题单独提出来问一下。 - radiantRazor

3
这里发生了什么?这两个代码路径完全等价,不是吗?
它们并不等价;三元运算符有一些注意事项。
三元运算符的真条件参数 (long) 0 是基本类型 long。因此,假条件参数将自动从 Long 拆箱为 long(根据 JLS §15.25):

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

然而,这个参数是null的(因为你的映射中不包含字符串"non-existent key",意味着get()返回null),因此在拆箱过程中会发生NullPointerException

但是为什么if-true值的类型会影响if-false值的类型呢?这不应该由左侧的类型确定吗? - radiantRazor
1
@radiantRazor 简而言之,因为这是JLS定义三元运算符的方式。 - arshajii

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没有像这样简单的东西,但是可以使用Maps.transformValues()方法来包装map


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接