【问题标题】:Weird behaviour when using Java ternary operator使用 Java 三元运算符时的奇怪行为
【发布时间】:2013-07-09 13:38:13
【问题描述】:

当我这样写我的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 这只是我的代码的简化版本。有问题的地图实际上是从另一个按需动态创建的地方检索的。这就是为什么要检查。在这里我包括了初始化,以便回答我的问题时没有执行 if-true 案例的人清楚
  • 我现在明白了。如果有人将 null 映射传递给您的代码,最好的办法可能是立即抛出异常。或者,如果有人将 null 传递给您的代码,请将您的地图设置为空地图。

标签: java nullpointerexception ternary-operator


【解决方案1】:

它们不等价。

这个表达式的类型

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

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

这个表达式是long类型的原因来自§15.25 of the JLS部分:

如果第二个和第三个操作数之一是基本类型T,而另一个的类型是对T 应用装箱转换(第 5.1.7 节)的结果,则条件表达式的类型是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 的情况。

【讨论】:

  • 在这种情况下,发帖者是否可以通过将 0 转换为 Long 而不是 long 来使用三元运算符?
  • 是的,不过他必须将0L 转换为Long
  • 哇,JLS 有这么多隐藏的陷阱。我明显的后续问题是为什么将 JLS 定义为将类型推断为原始类型?如果定义该类型应被推断为自动装箱类型,则可以避免此类空指针异常。我会尝试将其作为一个单独的问题提出
【解决方案2】:

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

它们不等价;三元运算符有一些注意事项。

三元运算符(long) 0 的if-true 参数属于基本类型long。因此,if-false 参数将自动从Long 拆箱到long(根据JLS §15.25):

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

但是,这个参数是null(因为你的地图不包含字符串"non-existent key",意思是get()返回null),所以在拆箱过程中会出现NullPointerException

【讨论】:

  • 但是为什么if-true值的类型会影响if-false值的类型呢?这不应该由 LHS 上的类型决定吗?
  • @radiantRazor 简而言之,因为这就是 JLS 定义三元运算符的方式。
【解决方案3】:

我在上面评论建议他确保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

【讨论】:

    猜你喜欢
    • 2016-07-14
    • 1970-01-01
    • 2013-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-20
    相关资源
    最近更新 更多