public class Npe {
static class Thing {
long value;
}
public static Map<Thing, Long> map;
public static void main(String[] args) {
Thing thing = new Thing();
method(null); // returns -1
method(thing); // returns 0
map = new HashMap<Thing, Long>();
method(null); // returns -1
method(thing); // NullPointerException thrown inside this method call
}
public static long method(Thing thing) {
if (thing == null) {
return -1;
}
Long v = (map == null) ? thing.value : map.get(thing); // NPE here
if (v == null) {
v = thing.value;
}
return v;
}
}
在第4次致电method()
时,我会在NullPointerException
内的指定行上抛出method()
。如果我从
Long v = (map == null) ? thing.value : map.get(thing);
到
Long v;
if (map == null) {
v = thing.value;
} else {
v = map.get(thing);
}
我没有NullPointerException
,方法的行为应该如此。问题是:为什么??
在我看来,编译器希望?
运算符的结果为long
,以便它自动取消装箱(从Long
降级到long
)调用map.get(thing)
的结果(可能会返回null
,因此会抛出NullPointerException
)。恕我直言,它应该期望?
运算符的结果为Long
并自动装箱(将long
提升为Long
)thing.value
。
更好的是,如果我重构这句话:
Long v = (map == null) ? thing.value : map.get(thing);
到此(明确地将long
投射到Long
):
Long v = (map == null) ? (Long)thing.value : map.get(thing);
我的IDE(IntelliJ)说演员阵容是多余的,但编译后的代码按预期工作,不会抛出NullPointerException
! :-D
答案 0 :(得分:29)
考虑你的条件表达式:
(map == null) ? thing.value : map.get(thing)
该表达式的结果为long
,因为thing.value
的类型为long
。见JLS §15.25 - Conditional Operator。 JLS 8中的表格是一个很好的补充。它阐明了不同输入类型的所有可能输出类型。与条件表达式类型相关的混淆程度如此之多。
现在,当您调用此方法时:
method(thing);
map
不是null
,因此表达式中的条件map == null
的计算结果为false
,然后评估map.get(thing)
以获得结果。
由于map
尚无条目,map.get(thing)
将返回null
。但由于结果类型为long
,因此会对null
执行取消装箱操作,从而产生NPE
。
现在,当您明确地将thing.value
投射到Long
时,表达式的类型变为Long
。因此,对map.get(thing)
的结果不执行取消装箱,null
已分配给Long v
。
答案 1 :(得分:0)
这是我对正在发生的事情的理解:
使用Long v = (map == null) ? thing.value : map.get(thing); // NPE here
map.get(thing)
返回Long
null
,然后尝试将其值展开到long
(因为表达式类型,如果长) - 这会导致NPE。
但是,当您使用长格式时,您要小心避免对null Long进行拆箱操作。