Java 8 方法参考 class 实例方法 NPE

Java 8 method reference to class instance method NPE

import java.util.function.Function;

public class Playground {
    public static void main (String[] args) {
        Object o = null;
        System.out.println(o);
        Function<Object, String> toStringFunc = Object::toString;
        String s = toStringFunc.apply(o);
        System.out.println(s);
    }
}

此代码将导致 NullPointerException 被抛出,在包含 toStringFunc.apply(o).

的行报告

这是一个简单的例子,所以很容易看出 o == null,但是我们一般如何理解为什么这行代码会抛出 NPE,因为 toStringFunc,唯一的变量在该行中被取消引用,不为空。

当你

toStringFunc.apply(o);

这与

相同
o.toString()

这就是为什么你得到 NullPointerException 因此,您必须确保您的对象不为空。

通常,您会查看最深的堆栈跟踪条目,以找出在相应行中取消引用了哪个变量。 你是对的,当堆栈跟踪看起来像

时,这是不可能的
Exception in thread "main" java.lang.NullPointerException
        at Playground.main(Playground.java:9)

问题是在 main 方法的这一行中,实际取消引用并没有发生。 它发生在调用的 apply 方法中,该方法的实现是生成的 JRE 的一部分 class 并且其堆栈帧已从跟踪中省略。

情况并非总是如此。这是 JDK-8025636: Hide lambda proxy frames in stacktraces 的结果。 中也讨论了此更改。

对于 lambda 表达式,隐藏工作很顺利,例如如果你使用

import java.util.function.Function;

public class Playground {
    public static void main (String[] args) {
        Object o = null;
        System.out.println(o);
        Function<Object, String> toStringFunc = obj -> obj.toString();
        String s = toStringFunc.apply(o);
        System.out.println(s);
    }
}

相反,堆栈跟踪看起来像

Exception in thread "main" java.lang.NullPointerException
        at Playground.lambda$main[=12=](Playground.java:8)
        at Playground.main(Playground.java:9)

显示取消引用发生的确切位置,同时省略了调用者 (main) 和被调用者 (lambda$main[=17=]) 之间不相关的生成方法。

不幸的是,对于直接调用目标方法而不借助其他可见方法的方法引用,这并不顺利。 这适得其反,尤其是在目标方法不在跟踪中的情况下,因为调用本身失败了,例如当接收者实例为 null 时。 当在调用目标方法之前或之后在生成的代码中尝试取消装箱 null 时,可能会出现类似的问题。

一个解决方案是 运行 带有选项的 JVM
-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames 禁用堆栈帧的隐藏。这可能会导致更长的堆栈跟踪,因为它还会影响其他绑定代码,例如进行反思。 所以只有当你怀疑某个异常不是发生在报告的地方,而是发生在隐藏的框架时,你才可以使用这个选项。将此选项与原始代码一起使用会产生:

Exception in thread "main" java.lang.NullPointerException
        at Playground$$Lambda/321001045.apply(<Unknown>:1000001)
        at Playground.main(Playground.java:9)

class 和方法的名称可能有所不同,但可以识别为生成的代码。从此堆栈跟踪中,您可以得出结论,不是在第 9 行 main 取消引用的变量,而是传递给调用的参数之一必须是 null.