NullPointerException 而不是 null(JVM 错误?)

NullPointerException instead of null (JVM Bug?)

我在当前版本的 Java8 中发现了一个奇怪的行为。在我看来,下面的代码应该没问题,但是 JVM 会抛出一个 NullPointerException:

Supplier<Object> s = () -> false ? false : false ? false : null;
s.get(); // expected: null, actual: NullPointerException

不管是什么lambda表达式(和java.util.function.Function一样)或者使用什么泛型都无所谓。 false ? : 也可以有更有意义的表达。上面的例子很短。这是一个更丰富多彩的例子:

Function<String, Boolean> f = s -> s.equals("0") ? false : s.equals("1") ? true : null;
f.apply("0"); // false
f.apply("1"); // true
f.apply("2"); // expected: null, actual: NullPointerException

但是这些代码片段 运行 很好:

Supplier<Object> s = () -> null;
s.get(); // null

Supplier<Object> s = () -> false ? false : null;
s.get(); // null

或函数:

Function<String, Boolean> f = s -> {
    if (s.equals("0")) return false;
    else if (s.equals("1")) return true;
    else return null;
};
f.apply("0"); // false
f.apply("1"); // true
f.apply("2"); // null

我测试了两个 Java 版本:

~# java -version

openjdk version "1.8.0_66-internal" OpenJDK Runtime Environment (build 1.8.0_66-internal-b01) OpenJDK 64-Bit Server VM (build 25.66-b01, mixed mode)

C:\>java -version

java version "1.8.0_51" Java(TM) SE Runtime Environment (build 1.8.0_51-b16) Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)

这与lambda表达式无关;只是在这种情况下,三元运算符的 return 类型是 boolean,因此将使用自动拆箱。

这里也抛出NPE:

public class Main {

    private static Object method() {
        return false ? false : false ? false : null;
    }

    public static void main(String[] args) {
        System.out.println(method());
    }
}

那么,这里到底发生了什么?

首先根据JLS§15.25:

对'embedded'表达式(false ? false : null)求值

The conditional operator is syntactically right-associative (it groups right-to-left). Thus, a?b:c?d:e?f:g means the same as a?b:(c?d:(e?f:g)).

嵌入表达式的类型是Boolean(加框boolean),这样falsenull都可以放进去

那么整个表达式就是:

false ? false : (Boolean expression)

然后,再根据JLS §15.25:

If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.

所以,第一个参数是原始类型boolean(规范中的T),另一个参数是盒装TBoolean) , 所以整个表达式的类型是 boolean.

然后,在运行时,嵌入表达式的计算结果为 null,自动拆箱为 boolean,导致 NPE。