web-dev-qa-db-ja.com

java.lang.NullPointerExceptionは、ラムダ式ではなく、メソッド参照を使用してスローされます

Java 8メソッド参照を使用して、未処理の例外について奇妙なことに気づきました。これはラムダ式() -> 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();
    }
}

「例外」を出力するので、正常に動作します。しかし、メソッド参照を使用するように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)

誰かがここで何が起こっているのか説明できますか?

46

この動作は、メソッド参照の評価プロセスとラムダ式の微妙な違いに依存しています。

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に評価され、そのメソッド参照が評価されたときに例外がスローされます。ただし、このコードは後で実行されるため、その時点では例外ハンドラはアタッチされていません。

ラムダは本体が実行されずに評価されるため、ラムダ式の場合はこれは発生しません。 From ラムダ式の実行時評価

ラムダ式の評価は、ラムダ本体の実行とは異なります。

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

snullであっても、ラムダ式は正しく作成されます。次に、例外ハンドラーがアタッチされ、スレッドが開始して例外をスローし、ハンドラーによってキャッチされます。


補足として、Eclipse Mars.2にはこれに関する小さなバグがあるようです。たとえメソッド参照があっても、例外ハンドラーを呼び出します。 Eclipseは、必要なときにs::toLowerCaseNullPointerExceptionをスローしないため、後で例外ハンドラーが追加されたときに例外が遅延されます。

55
Tunaki

ワオ。興味深いものを発見しました。以下を見てみましょう:

_Function<String, String> stringStringFunction = String::toLowerCase;
_

これは、タイプStringのパラメーターを受け入れ、入力パラメーターの小文字である別のStringを返す関数を返します。これはs.toLowerCase()と多少同じですが、sは入力パラメーターです。

_stringStringFunction(param) === param.toLowerCase()
_

_Function<Locale, String> localeStringFunction = s::toLowerCase;
_

LocaleからStringまでの関数です。これは、s.toLowerCase(Locale)メソッド呼び出しと同等です。これは、内部では2つのパラメーターで機能します。1つはsで、もう1つはロケールです。 snullの場合、この関数を作成するとNullPointerExceptionがスローされます。

_localeStringFunction(locale) === s.toLowerCase(locale)
_

次は

_Runnable r = () -> s.toLowerCase()
_

これはRunnableインターフェイスの実装であり、実行すると、指定された文字列toLowerCaseに対してメソッドsを呼び出します。

だからあなたの場合

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

_s::toLowerCase_の呼び出しの結果をそれに渡す新しいThreadを作成しようとします。しかし、これはNPEを一度にスローします。スレッドが開始される前でも。したがって、NPEは、スレッドtからではなく、現在のスレッドでスローされます。これが、例外ハンドラが実行されない理由です。

7
Nikem