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

随声附和 提交于 2019-12-17 05:01:17

问题


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?


回答1:


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, a NullPointerException 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.




回答2:


Wow. You have uncovered something interesting. Let us take a look at the following:

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

This returns us a function which accepts on parameter of type String and returns another String, which is a lowercase of the input parameter. This is somewhat equivalent to the s.toLowerCase(), where s is the input parameter.

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

Next

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

is a function from Locale to String. This is equivalent to s.toLowerCase(Locale) method invocation. It works, under the hood, on 2 parameters: one is s and another is some locale. If s is null, then this function creation throws a NullPointerException.

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

Next is

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

Which is an implementation of Runnable interface which, when executed, will call method toLowerCase on given string s.

So in your case

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

tries to create a new Thread passing the result of the invocation of s::toLowerCase to it. But this throws a NPE at once. Even before thread is started. And so NPE is thrown in your current thread, not from inside thread t. This is why your exception handler is not executed.



来源:https://stackoverflow.com/questions/37413106/java-lang-nullpointerexception-is-thrown-using-a-method-reference-but-not-a-lamb

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!