NullPointerException,在三元表达式中具有自动装箱function

运行以下Java代码:

boolean b = false; Double d1 = 0d; Double d2 = null; Double d = b ? d1.doubleValue() : d2; 

为什么会出现NullPointerException?

条件表达式b ? d1.doubleValue : d2的返回类型b ? d1.doubleValue : d2 b ? d1.doubleValue : d2double 。 条件表达式必须具有单个返回类型。 遵循二进制数字提升的规则, d2被自动装箱为double ,当d2 == null时会导致NullPointerException

从语言规范,第15.25节:

否则,如果第二个和第三个操作数具有可转换的类型(第5.1.8节)到数字类型,那么有几种情况:…

否则,二进制数字提升(第5.6.2节)将应用于操作数类型,条件表达式的类型是第二个和第三个操作数的提升类型。 请注意,二进制数字促销执行拆箱转换(第5.1.8节)和值集转换(第5.1.13节)。

因为周围的两个表达式:必须返回相同的类型。 这意味着Java尝试将表达式d2转换为double 。 这意味着字节码在d2 – > NPE上调用doubleValue()

通常应该避免混合类型计算; 用?:复合?:条件/三元只会使情况变得更糟。

以下是Java Puzzlers ,Puzzle 8:Dos Equis的引用:

混合型计算可能令人困惑。 这比条件表达更明显。 […]

用于确定条件表达式的结果类型的规则太长且复杂而不能完整地再现,但这里有三个关键点。

  1. 如果第二个和第三个操作数具有相同的类型,那么这是条件表达式的类型。 换句话说,你可以通过避开混合型计算来避免整个混乱。

  2. 如果其中一个操作数是T类型,其中Tbyteshortchar ,另一个操作数是int类型的常量表达式,其值在类型T中是可表示的,条件表达式的类型是T.

  3. 否则,二进制数字提升将应用于操作数类型,条件表达式的类型是第二个和第三个操作数的提升类型。

这里应用了第3点,它导致了拆箱。 当unbox null ,自然会抛出NullPointerException

这是混合型计算的另一个例子和?:这可能是令人惊讶的:

  Number n = true ? Integer.valueOf(1) : Double.valueOf(2); System.out.println(n); // "1.0" System.out.println(n instanceof Integer); // "false" System.out.println(n instanceof Double); // "true" 

混合类型计算是至少3个Java Puzzler的主题。

最后,这是Java Puzzlers规定的内容:

4.1。 混合型计算令人困惑

处方 :避免混合型计算。

?:运算符与数字操作数一起使用时,对第二个和第三个操作数使用相同的数字类型。


更喜欢原始类型到盒装基元

以下是Effective Java 2nd Edition的引用,第49项:首选原始类型为盒装基元

总之,只要您有选择,就可以优先使用原始元素。 原始类型更简单,更快捷。 如果你必须使用盒装基元,小心! 自动装箱减少了使用盒装基元的冗长度,但没有降低危险性。 当你的程序将两个盒装基元与==运算符进行比较时,它会进行身份比较,这几乎肯定不是你想要的。 当您的程序执行涉及盒装和未装箱原语的混合类型计算时,它会进行拆箱,当您的程序进行拆箱时,它会抛出NullPointerException 。 最后,当您的程序框原始值时,它可能导致代价高昂且不必要的对象创建。

有些地方你别无选择,只能使用盒装基元,例如generics,但是你应该认真考虑使用盒装基元的决定是否合理。

相关问题

  • Java / C#中的int和Integer有什么区别?
  • 为什么Java中的自动装箱允许我为布尔值提供3个可能的值?
  • 是否保证Java中的新Integer(i)== i? (是!!!)
  • 在Java中比较两个整数时会发生自动拆箱吗? (没有!!!)
  • Java noob:仅针对对象的generics? (是的,很不幸的)
  • 为什么int num = Integer.getInteger(“123”)抛出NullPointerException?