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:\>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
),所以false
和null
都可以放进去。
那么整个表达式就是:
false ? false : (Boolean expression)
然后,再次根据 JLS §15.25:
如果第二个和第三个操作数之一是原始类型 T,并且 另一个的类型是应用拳击转换的结果 (§5.1.7) 到 T,则条件表达式的类型为 T。
所以,第一个参数是原始类型boolean
(规范中的T
),另一个是装箱的T
(Boolean
),所以整个表达式的类型是@ 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