I've noticed something weird about unhandled exceptions using Java 8 method reference. This is my code, using the lambda expression () -> 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(); } }
It prints "Exception", so it works fine. But when I change Thread t
to use a method-reference (even IntelliJ suggests that):
Thread t = new Thread(s::toLowerCase);
the exception is not being caught:
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)
Can someone explain what is going on here?
This behaviour relies on a subtle difference between the evaluation process of method-references and lambda expressions.
From the 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.
With the following code:
Thread t = new Thread(s::toLowerCase); // <-- s is null, NullPointerException thrown here t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));
the expression s
is evaluated to null
and an exception is thrown exactly when that method-reference is evaluated. However, at that time, no exception handler was attached, since this code would be executed after.
This doesn't happen in the case of a lambda expression, because the lambda will be evaluated without its body being executed. From 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!"));
Even if s
is null
, the lambda expression will be correctly created. Then the exception handler will be attached, the thread will start, throwing an exception, that will be caught by the handler.
As a side-note, it seems Eclipse Mars.2 has a small bug regarding this: even with the method-reference, it invokes the exception handler. Eclipse isn't throwing a NullPointerException
at s::toLowerCase
when it should, thus deferring the exception later on, when the exception handler was added.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With