运行以下Java代码:
boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1.doubleValue() : d2;
为什么会出现NullPointerException?
答案 0 :(得分:33)
条件表达式b ? d1.doubleValue : d2
的返回类型是double
。条件表达式必须具有单个返回类型。遵循二进制数字促销规则,d2
会自动装箱到double
,这会导致NullPointerException
d2 == null
。{/ p>
从语言规范,§15.25部分:
否则,如果是第二个和第三个 操作数有类型 可转换(第5.1.8节)到数字类型, 然后有几种情况:......
否则,将应用二进制数字提升(第5.6.2节) 到操作数类型,以及类型 条件表达式是 推广第二和第三类型 操作数。请注意二进制数字 促销执行拆箱转换 (§5.1.8)和价值集转换 (§5.1.13)。
答案 1 :(得分:15)
因为:
周围的两个表达式必须返回相同的类型。这意味着Java尝试将表达式d2
转换为double
。这意味着字节码在doubleValue()
上调用d2
- > NPE。
答案 2 :(得分:4)
通常应该避免混合类型计算;将此与?:
条件/三元混合只会使情况变得更糟。
以下是来自Java Puzzlers,引文8:Dos Equis:
的引用混合型计算可能令人困惑。这比条件表达更明显。 [...]
确定条件表达式的结果类型的规则太长且复杂,无法完整复制,但这里有三个关键点。
如果第二个和第三个操作数具有相同的类型,那么这是条件表达式的类型。换句话说,你可以通过避开混合型计算来避免整个混乱。
如果其中一个操作数的类型为 T ,其中 T 为
byte
,short
或char
,另一个操作数是int
类型的常量表达式,其值在 T 类型中具有代表性,条件表达式的类型为 T 。- 醇>
否则,二进制数字提升将应用于操作数类型,条件表达式的类型是第二个和第三个操作数的提升类型。
此处应用了第3点,导致取消装箱。当您取消装箱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 Puzzlers 的主题。
最后,这是 Java Puzzlers 规定的内容:
4.1。混合型计算令人困惑
处方:避免混合型计算。
将
?:
运算符与数字操作数一起使用时,对第二个和第三个操作数使用相同的数字类型。
以下是 Effective Java 2nd Edition的引用,第49项:首选原始类型为盒装基元:
总之,只要有选择,就可以优先使用原始元素。原始类型更简单,更快捷。如果你必须使用盒装基元,小心!自动装箱减少了使用盒装基元的冗长,但没有降低危险。当您的程序将两个盒装基元与
==
运算符进行比较时,它会进行身份比较,这几乎肯定不是您想要的。当您的程序执行涉及盒装和未装箱原语的混合类型计算时,它会进行拆箱,当您的程序取消装箱时,它可以抛出NullPointerException
。最后,当您的程序框原始值时,它可能导致代价高昂且不必要的对象创建。
有些地方你别无选择,只能使用盒装基元,例如:泛型,但除此之外你应该认真考虑是否有合理使用盒装基元的决定。
答案 3 :(得分:0)
为以下两种情况返回相同的类型,您将得到结果。
boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1 : (Double)d2;
System.out.println(d);