NullPointerException 而不是 null(JVM 错误?)

Posted

技术标签:

【中文标题】NullPointerException 而不是 null(JVM 错误?)【英文标题】:NullPointerException instead of null (JVM Bug?) 【发布时间】:2015-11-05 08:34:54 【问题描述】:

我在当前版本的 Java 8 中发现了一个奇怪的行为。在我看来,下面的代码应该没问题,但是 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 版本“1.8.0_66-internal” OpenJDK 运行时环境 (build 1.8.0_66-internal-b01) OpenJDK 64 位服务器 VM(内部版本 25.66-b01,混合模式)

C:\&gt;java -version

java 版本“1.8.0_51” Java(TM) SE 运行时环境 (build 1.8.0_51-b16) Java HotSpot(TM) 64 位服务器 VM(内部版本 25.51-b03,混合模式)

【问题讨论】:

这与Object a = false ? false : false ? false : null; 有何不同?NullPointerException 也产生NullPointerException 它出现在 Java 7 和 Java 8 (OpenJDK) 中。 @bayou.io 怎么样? Lambdas 不添加任何东西。 “问题”是三元表达式。 我认为这个问题的答案是将null 拆箱为boolean 值,因为在使用两个?: 运算符时具有绑定/优先级。重复的帖子说了很多(也许不是关于绑定,但暗示了它)。没有必要为此发表 10 篇不同的帖子。如果您有更好的副本,我很乐意重新打开,我们可以重新关闭它。这个问题已经回答了很多次了。 @steffen - 显然某处有拆箱。但目前还不清楚为什么;或者它是合法的还是错误的。我敢打赌,即使是语言设计师也无法从头顶上回答这个问题。 java8 确实让?: 更加复杂。看我的分析here 【参考方案1】:

这与 lambda 表达式无关;只是这种情况下三元运算符的返回类型是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 评估“嵌入”表达式 (false ? false : null):

条件运算符在语法上是右结合的(它分组 右到左)。因此,a?b:c?d:e?f:g 的含义与 a?b:(c?d:(e?f:g))。

嵌入表达式的类型是Boolean(装箱boolean),所以falsenull都可以放进去。

那么整个表达式就是:

false ? false : (Boolean expression)

然后,再次根据 JLS §15.25:

如果第二个和第三个操作数之一是原始类型 T,并且 另一个的类型是应用拳击转换的结果 (§5.1.7) 到 T,则条件表达式的类型为 T。

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

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

【讨论】:

嗯,OP的代码在java8中,所以要考虑目标类型Object,还要分析是否应该影响操作数;为什么要拆箱而不是装箱。 那为什么false ? false : false ? Boolean.FALSE : null; 仍然抛出一个NPE。嵌套表达式的第二个操作数是Boolean,因此不会应用装箱转换(第 5.1.7 节)。外部表达式为false ? false : (Boolean expression)。 JLS §15.25 中的下一行显示为 If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type. OK,但最终将等于 false ? false : (Boolean) null false ? false : false ? Boolean.FALSE : null 是一回事。嵌入的三元表达式计算结果为Boolean,整个表达式返回boolean,因为它是上面引用的原始类型T 关于你引用的段落,表达式中没有出现这种情况,false不是引用类型,(Boolean) null不是空类型。 引用的JLS语句适用于false ? false : false ? Boolean.FALSE : null(2,3个操作数之一是null,另一个是引用类型)。因此,内部表达式的类型是引用类型(Boolean,没有自动装箱)。因此整个表达式是boolean ? boolean : Boolean,它给出了一个NPE。但是关于最初的问题,您的解释是完美的 (+1),至少在 bayou.io 没有找到 Java 8 的细节时。

以上是关于NullPointerException 而不是 null(JVM 错误?)的主要内容,如果未能解决你的问题,请参考以下文章

当 GET 返回 null 而不是 List 时 KotlinxSerializer 出现 NullPointerException

java.lang.NullPointerException 是使用方法引用而不是 lambda 表达式引发的

java.lang.NullPointerException Spring Mvc

JavaFx MVC Getter 抛出 NullPointerException

登录 NullPointerException 时 Spring Security 无法访问 userService

从实用程序类(非活动)访问房间数据库的 NullPointerException