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

我注意到使用Java 8方法引用的未处理的异常有些奇怪。这是我的代码,使用lambda表达式:() -> s.toLowerCase()

public class Test {

    public static void main(String[] args) {
        testNPE(null);
    }

    private static void testNPE(String s) {
        Thread t = new Thread(() -> s.toLowerCase());
//        Thread t = new Thread(s::toLowerCase);
        t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));
        t.start();
    }
}

它打印“异常”,因此工作正常。但是当我改变使用方法引用时(甚至IntelliJ也建议):Thread t

Thread t = new Thread(s::toLowerCase);

未捕获异常:

Exception in thread "main" java.lang.NullPointerException
    at Test.testNPE(Test.java:9)
    at Test.main(Test.java:4)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

有人可以解释一下这里发生了什么吗?


答案 1

此行为依赖于方法引用的计算过程与 lambda 表达式之间的细微差别。

来自方法引用的 JLS 运行时评估

首先,如果方法引用表达式以 ExpressionName 或 Primary 开头,则计算此子表达式。如果子表达式的计算结果为 null,则会引发 NullPointerException,并且方法引用表达式会突然完成

使用以下代码:

Thread t = new Thread(s::toLowerCase); // <-- s is null, NullPointerException thrown here
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));

表达式的计算结果为,并且在计算该方法引用时正好引发异常。但是,当时没有附加异常处理程序,因为此代码将在之后执行。snull

对于 lambda 表达式,不会发生这种情况,因为 lambda 将在不执行其主体的情况下进行计算。从 Lambda 表达式的运行时评估中

lambda 表达式的计算不同于 lambda 主体的执行。

Thread t = new Thread(() -> s.toLowerCase());
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));

即使 为 ,lambda 表达式也会被正确创建。然后异常处理程序将被附加,线程将启动,引发异常,该异常将被处理程序捕获。snull


顺便说一句,Eclipse Mars.2 似乎有一个小错误:即使使用方法引用,它也会调用异常处理程序。Eclipse 不会在应该的时间抛出 a,因此在以后添加异常处理程序时会推迟异常。NullPointerExceptions::toLowerCase


答案 2

哇。你发现了一些有趣的东西。让我们来看看以下内容:

Function<String, String> stringStringFunction = String::toLowerCase;

这给我们返回一个函数,它接受类型的参数并返回另一个 ,这是输入参数的小写。这在某种程度上等效于 输入参数,where 是输入参数。StringStrings.toLowerCase()s

stringStringFunction(param) === param.toLowerCase()

下一个

Function<Locale, String> localeStringFunction = s::toLowerCase;

是从 到 的函数。这等效于方法调用。它在引擎盖下对2个参数起作用:一个是,另一个是某个区域设置。如果 是 ,则此函数创建将抛出一个 .LocaleStrings.toLowerCase(Locale)ssnullNullPointerException

localeStringFunction(locale) === s.toLowerCase(locale)

接下来是

Runnable r = () -> s.toLowerCase()

这是接口的实现,当执行时,它将在给定字符串上调用方法。RunnabletoLowerCases

所以在你的情况下

Thread t = new Thread(s::toLowerCase);

尝试创建一个新的传递调用的结果。但这立刻就抛出了一个。甚至在线程启动之前。因此,它被抛入您当前的线程中,而不是从内部线程。这就是不执行异常处理程序的原因。Threads::toLowerCaseNPENPEt


推荐