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

查看:30
本文介绍了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: Hide lambda proxy frames in stacktraces 的结果.本问答中也讨论了此更改.

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天全站免登陆