Java 8方法对类实例方法NPE的引用 [英] Java 8 method reference to class instance method NPE

查看:116
本文介绍了Java 8方法对类实例方法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)的行中报告.

This code will result in a NullPointerException being thrown, reported at the line containing toStringFunc.apply(o).

这是一个简单的示例,因此很容易看到o == null,但是总的来说,我们如何才能理解为什么这行代码会抛出NPE,如toStringFunc一样,这是该行中唯一被取消引用的变量,不为空.

This is a trivial example, so it's easy to see that o == null, but how in general can we understand why this line of code would throw a NPE, as toStringFunc, the only variable being dereferenced in that line, is not null.

推荐答案

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

Normally, you would look at the deepest stack trace entry to find out which variable has been dereferenced in the corresponding line. You are right in that this is not possible here when the stack trace looks like

Exception in thread "main" java.lang.NullPointerException
        at Playground.main(Playground.java:9)

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

The problem is that in this line in the main method, the actual dereferencing did not happen. It happens within the invoked apply method whose implementation is part of a JRE generated class and whose stack frame has been omitted from the trace.

并非总是如此.这是 JDK-8025636:在堆栈跟踪中隐藏Lambda代理框架的结果. 此问题与解答中也对此进行了讨论.

This was not always the case. It’s the result of JDK-8025636: Hide lambda proxy frames in stacktraces. This change has been discussed in this Q&A as well.

对于lambda表达式,例如,如果您使用过

The hiding works smoothly for lambda expressions, e.g. if you used

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);
    }
}

相反,堆栈跟踪看起来像

instead, the stack trace looked like

Exception in thread "main" java.lang.NullPointerException
        at Playground.lambda$main$0(Playground.java:8)
        at Playground.main(Playground.java:9)

显示省略调用者(main)和被调用者(lambda$main$0)之间无关的生成方法时取消引用发生的确切位置.

showing the exact place where the dereferencing happened while the irrelevant generated method mediating between the caller (main) and the callee (lambda$main$0) has been omitted.

不幸的是,对于直接引用目标方法而不借助另一个可见方法的方法引用来说,这种方法无法顺利进行. 这特别适得其反,因为在调用方法本身失败的情况下,目标方法不在跟踪中,例如.当接收者实例为null时. 在调用目标方法之前或之后,尝试在生成的代码中取消对null的装箱尝试时,可能会发生类似的问题.

Unfortunately, this doesn’t work that smooth for method references where the target method is invoked directly without the aid of another visible method. This backfires especially in cases where the target method is not in the trace as the invocation itself failed, e.g. when the receiver instance is null. Similar problems may occur when an attempt to unbox null happened in the generated code before or after the invocation of the target method.

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

One solution is to run the JVM with the options
-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames to disable the hiding of stack frames. This may cause much longer stack traces as it also affects other binding code, e.g. for Reflection. So you may only use this option when you have the suspicion that a certain exception did not happen at the reported place, but at a hidden frame. Using this option with you original code yields:

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

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

The name of the class and method may vary, but it’s recognizable as generated code. From this stack trace, you can conclude that not a variable being dereferenced at main, line 9, but rather one of the arguments passed to the invocation must have been null.

这篇关于Java 8方法对类实例方法NPE的引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆