25

运行以下 Java 代码:

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

为什么会出现 NullPointerException?

4

4 回答 4

37

条件表达式的返回类型b ? d1.doubleValue : d2double。条件表达式必须有一个返回类型。遵循二进制数字提升的规则,d2自动拆箱到 a double,这会导致NullPointerExceptionwhen d2 == null

从语言规范第 15.25 节:

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

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

于 2010-07-16T14:39:18.907 回答
15

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

于 2010-07-16T14:38:01.547 回答
4

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

这是来自Java Puzzlers的引述,Puzzle 8: Dos Equis:

混合类型的计算可能会令人困惑。这在条件表达式中表现得最为明显。[...]

确定条件表达式结果类型的规则太长太复杂,无法完整重现,但这里有三个关键点。

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

  2. 如果其中一个操作数是T类型,其中Tbyteshort或,而另一个操作数是其值可在类型Tchar中表示的类型的常量表达式,则条件表达式的类型是Tint

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

此处应用了第 3 点,它导致了拆箱。当你 unbox 时null,自然NullPointerException会抛出 a 。

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

    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 Puzzlers的主题。

最后,这是Java Puzzlers的规定:

4.1。混合类型的计算令人困惑

处方:避免混合类型的计算。

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


关于偏爱原始类型而不是盒装原始类型

这是来自Effective Java 2nd Edition, Item 49: Prefer primitive types to boxed primitives的引述:

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

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

相关问题

于 2010-07-16T14:58:35.253 回答
0

为两种情况返回相同的类型,如下所示,您将得到结果。

boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1 : (Double)d2;
System.out.println(d);
于 2019-07-08T14:38:56.090 回答