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)
誰かがここで何が起こっているのか説明できますか?
この動作は、メソッド参照の評価プロセスとラムダ式の微妙な違いに依存しています。
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!"));
式s
はnull
に評価され、そのメソッド参照が評価されたときに例外がスローされます。ただし、このコードは後で実行されるため、その時点では例外ハンドラはアタッチされていません。
ラムダは本体が実行されずに評価されるため、ラムダ式の場合はこれは発生しません。 From ラムダ式の実行時評価 :
ラムダ式の評価は、ラムダ本体の実行とは異なります。
Thread t = new Thread(() -> s.toLowerCase());
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));
s
がnull
であっても、ラムダ式は正しく作成されます。次に、例外ハンドラーがアタッチされ、スレッドが開始して例外をスローし、ハンドラーによってキャッチされます。
補足として、Eclipse Mars.2にはこれに関する小さなバグがあるようです。たとえメソッド参照があっても、例外ハンドラーを呼び出します。 Eclipseは、必要なときにs::toLowerCase
でNullPointerException
をスローしないため、後で例外ハンドラーが追加されたときに例外が遅延されます。
ワオ。興味深いものを発見しました。以下を見てみましょう:
_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つはロケールです。 s
がnull
の場合、この関数を作成すると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
からではなく、現在のスレッドでスローされます。これが、例外ハンドラが実行されない理由です。