为什么此分配会导致 NPE?

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()NullPointerExceptionmethod()

Long v = (map == null) ? thing.value : map.get(thing);

Long v;
if (map == null) {
    v = thing.value;
} else {
    v = map.get(thing);
}

我得到没有,并且该方法的行为符合预期。问题是:为什么??NullPointerException

在我看来,编译器期望运算符的结果,以便它自动取消装箱(从降级到)调用的结果(可能会返回并因此抛出一个)。恕我直言,它应该期望操作员的结果和自动装箱(晋升为)相反。?longLonglongmap.get(thing)nullNullPointerException?LonglongLongthing.value

更好的是,如果我重构这个语句:

Long v = (map == null) ? thing.value : map.get(thing);

到此(显式转换为):longLong

Long v = (map == null) ? (Long)thing.value : map.get(thing);

我的IDE(IntelliJ)说强制转换是多余的,但编译的代码按预期工作,不会抛出NullPointerException:-D


答案 1

考虑您的条件表达式:

(map == null) ? thing.value : map.get(thing)

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

现在,当您调用此方法时,如下所示:

method(thing);

不是 ,因此表达式中的条件的计算结果为 为 ,然后计算结果为 。mapnullmap == nullfalsemap.get(thing)

由于还没有条目,将返回。但是由于结果的类型是 ,在 上执行取消装箱操作,从而得到 。mapmap.get(thing)nulllongnullNPE


现在,当您显式转换为 时,表达式的类型将变为 。因此,不会对 的结果执行取消装箱,而是将其分配给 。thing.valueLongLongmap.get(thing)nullLong v


答案 2

这是我对正在发生的事情的理解:

当您使用时Long v = (map == null) ? thing.value : map.get(thing); // NPE here

返回 a is,然后尝试将其值拆箱到 (因为表达式类型如果长 ) - 这会导致 NPE。map.get(thing)Longnulllong

但是,当您使用长格式时,您将小心地避免对空 Long 执行取消装箱操作。