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

java.lang.NullPointerException is thrown using a method-reference but not a lambda expression

我注意到使用 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();
    }
}

它打印 "Exception",所以它工作正常。但是当我将 Thread t 更改为使用方法引用时(甚至 IntelliJ 也建议这样做):

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)

谁能解释一下这是怎么回事?

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

来自 JLS Run-Time Evaluation of Method References:

First, if the method reference expression begins with an ExpressionName or a Primary, this subexpression is evaluated. If the subexpression evaluates to null, a NullPointerException is raised, and the method reference expression completes abruptly.

使用以下代码:

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

表达式 s 被评估为 null 并且在评估该方法引用时恰好抛出异常。但是,当时没有附加异常处理程序,因为这段代码将在之后执行。

这不会发生在 lambda 表达式的情况下,因为 lambda 将在其主体未被执行的情况下被评估。来自 Run-Time Evaluation of Lambda Expressions:

Evaluation of a lambda expression is distinct from execution of the lambda body.

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

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


附带说明一下,Eclipse Mars.2 似乎有一个关于此的小错误:即使使用方法引用,它也会调用异常处理程序。 Eclipse 没有在应该的时候在 s::toLowerCase 处抛出 NullPointerException,因此在添加异常处理程序时推迟异常。

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

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

这个 returns 我们使用一个函数,它接受 String 类型的参数和 returns 另一个 String 类型的参数,它是输入参数的小写。这有点等同于 s.toLowerCase(),其中 s 是输入参数。

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

下一个

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

是从LocaleString的函数。这相当于 s.toLowerCase(Locale) 方法调用。它在幕后工作于 2 个参数:一个是 s,另一个是某个语言环境。如果 snull,则此函数创建会抛出 NullPointerException.

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

接下来是

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

这是 Runnable 接口的一个实现,当执行时,将在给定字符串 s.

上调用方法 toLowerCase

所以在你的情况下

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

尝试创建一个新的 Thread 并将调用 s::toLowerCase 的结果传递给它。但这会立即抛出一个 NPE 。甚至在线程启动之前。因此 NPE 是在您当前的线程中抛出的,而不是从内部线程 t 中抛出的。这就是您的异常处理程序未被执行的原因。