Java自动装箱和三元运算符的疯狂使用

24

刚刚花了几个令人沮丧的小时来调试这段代码:

    LinkedHashMap<String, Integer> rsrqs = new LinkedHashMap<String, Integer>();
    Integer boxedPci = 52;
    Integer boxedRsrq = boxedPci != null ? rsrqs.get(boxedPci.toString()) : -1;

上面的代码会产生一个NullPointerException异常。下面的代码则不会:
    LinkedHashMap<String, Integer> rsrqs = new LinkedHashMap<String, Integer>();
    Integer boxedPci = 52;
    Integer boxedRsrq = boxedPci != null ? rsrqs.get(boxedPci.toString()) : Integer.valueOf(-1);

唯一的区别是将-1用Integer.valueOf()包装。我相信一旦有人解释这段代码为什么会表现出这样的行为,我一定会拍自己的脑门..但是能否有人解释一下这段代码为什么会表现出这样的行为呢? :)
-- 编辑
想了想,我怀疑NPE来自rsrqs.get()返回null,然后Java尝试将其解压缩为int,然后再打包回一个Integer。 Integer.valueOf()强制Java执行unbox-box步骤。故事的道德:不要忽略Eclipse中的那些装箱警告 ;)

1
没有给我一个 NPE, 请检查代码。 - Sergio
@Chechus 给了我。 - Christian Tapia
三元运算符确实很奇怪。你是否也注意到,在Java中,if或while循环的条件需要用括号括起来,但在三元条件中不需要。今天我遇到了类似的问题 - 假设自动装箱会发生。为什么这段代码会导致空指针异常?我以为字符类可以…… - JGFMK
4个回答

34

三元表达式和其他表达式一样,其类型由编译器确定。如果三元表达式的两个部分看起来具有不同的类型,则编译器将尝试使用最不明确的选项找到一个共同的基础类型。在这种情况下,-1 是最不明确的,因此三元表达式的类型是 int。不幸的是,编译器不会根据接收变量使用类型推断。

然后求值表达式 rsrqs.get(boxedPci.toString()) 并强制转换为类型 int 以匹配三元表达式,但由于它是 null,因此抛出 NPE。

通过对 -1 进行装箱,三元表达式的值成为 Integer,因此您是空安全的。


经过测试,使用Java 1.8后结果不同(返回null而非NPE)。有什么想法? - Sergio
3
Java8的类型推断更好了。这是lambda表达式支持的副作用。我的猜测是三元表达式的类型现在受到接收方类型的影响(即它被赋给了一个Integer,所以让我们将表达式的类型设为Integer,停止尝试去解包空值)。 - skaffman
1
@skaffman:这似乎是实现更好的类型推断的副作用,但通过阅读规范,我有一种感觉,您不应该依赖它,因为“数字条件表达式”明确声明为非“多态表达式”,即不受目标类型的影响。 - Holger
实际上,这可能是一个javac的bug;而且规范也可能有问题。请参见https://dev59.com/sJDea4cB1Zd3GeqPYCw3#33182091中的最后一个示例。 - ZhongYu

12

可以从Java语言规范:15.25.条件运算符? :中得出解释。

从那里的表格中,您可以获得信息,即如果第二个操作数(rsrqs.get(boxedPci.toString()))是Integer类型,第三个操作数是int类型,则结果将是int类型。

然而,这意味着

Integer boxedRsrq = boxedPci != null ? rsrqs.get(boxedPci.toString()) : -1;

语义上与

Integer boxedRsrq = boxedPci != null ? ((int)rsrqs.get(boxedPci.toString())) : -1;

但这意味着,如果你从映射中得到了null,你会得到一个NullPointerException,这显然是会发生的。

如果你将第三个操作数强制转换为Integer,那么第二个操作数就永远不会被强制转换为int,也就不会出现NPE。


4

好的,Integer.valueOf(String) 返回一个 Integer 类型,而 -1 是一个基本数据类型的 int。第一个示例需要进行强制拆箱操作,因为其中一个项是基本数据类型。你也可以使用

Integer boxedRsrq = boxedPci != null ? 
    rsrqs.get(boxedPci.toString()) : (Integer) -1;

这将把-1封装成一个框。


3
1

这里的“is an int, not an Integer”指的是一个int类型,而不是Integer类型。因此,在Java中,将会将你的Integer类型拆箱成int类型,这就导致了NullPointerException异常。当你自动拆箱一个空的Integer类型时,就会引发NullPointerException异常(参考链接: 这里)。

但是,当你使用

 Integer.valueOf(-1) 

它不需要自动拆箱,这样就不会出现异常。


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