为什么这个任务会导致NPE?

Dav*_*ser 26 java ternary-operator nullpointerexception

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在第四次打电话给method()我时,我NullPointerException在指示的线上投掷method().如果我重构那条线

Long v = (map == null) ? thing.value : map.get(thing);
Run Code Online (Sandbox Code Playgroud)

Long v;
if (map == null) {
    v = thing.value;
} else {
    v = map.get(thing);
}
Run Code Online (Sandbox Code Playgroud)

我没有NullPointerException,方法的行为应该如此.问题是:为什么?

在我看来,编译器期望?运算符的结果long是自动取消装箱(降级Longlong)调用的结果map.get(thing)(可能返回null并因此抛出a NullPointerException).恕我直言应该期待的结果,?运营商要Long和自动装箱(推广longLong)thing.value代替.

更好的是,如果我重构这个陈述:

Long v = (map == null) ? thing.value : map.get(thing);
Run Code Online (Sandbox Code Playgroud)

这个(铸造longLong明确):

Long v = (map == null) ? (Long)thing.value : map.get(thing);
Run Code Online (Sandbox Code Playgroud)

我的IDE(IntelliJ)说,转换是多余的,但编译的代码按预期工作,并没有抛出NullPointerException!:-D

Roh*_*ain 29

考虑你的条件表达式:

(map == null) ? thing.value : map.get(thing)
Run Code Online (Sandbox Code Playgroud)

该表达式的结果将是long,因为类型thing.valuelong.参见JLS§15.25 - 条件运算符.JLS 8中的表格是一个很好的补充.它阐明了不同输入类型的所有可能输出类型.与条件表达式类型相关的混淆程度如此之多.

现在,当您调用此方法时:

method(thing);
Run Code Online (Sandbox Code Playgroud)

map不是null,这样的条件map == null在表达式求false,然后计算map.get(thing)得到的结果.

由于map尚未进入,map.get(thing)将返回null.但是由于结果的类型是long,执行了拆箱操作null,这导致了NPE.


现在,当你明确地投thing.valueLong,表达的类型变得Long.因此,不会对结果执行拆箱map.get(thing),并将null其分配给Long v.

  • *常见*Java gotcha :) (4认同)
  • @DavidWasser首先,JLS中明确列出的行为不应该算作bug.任何操作员都必须以这种或那种方式实施.这完全是一个设计决策,最好留给Java设计师如何实现特定的运算符. (4认同)
  • 但是我仍然认为这是一个Java错误.没有理由**降级**引用参数以匹配另一个(原始)参数而不是**促使**原始参数匹配另一个(引用)参数.那只是愚蠢的恕我直言.这可能会导致NPE在没有必要的情况下,并且没有我可以看到替代方案的好处. (2认同)