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!"));
即使s
是null
,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;
是从Locale
到String
的函数。这相当于 s.toLowerCase(Locale)
方法调用。它在幕后工作于 2 个参数:一个是 s
,另一个是某个语言环境。如果 s
是 null
,则此函数创建会抛出 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
中抛出的。这就是您的异常处理程序未被执行的原因。
我注意到使用 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
, aNullPointerException
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!"));
即使s
是null
,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;
是从Locale
到String
的函数。这相当于 s.toLowerCase(Locale)
方法调用。它在幕后工作于 2 个参数:一个是 s
,另一个是某个语言环境。如果 s
是 null
,则此函数创建会抛出 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
中抛出的。这就是您的异常处理程序未被执行的原因。