2016-05-24 10 views
34

मैंने जावा 8 विधि संदर्भ का उपयोग करके अनचाहे अपवादों के बारे में कुछ अजीब देखा है। यह लैम्ब्डा अभिव्यक्ति () -> s.toLowerCase() उपयोग करते हुए मेरे कोड है,:java.lang.NullPointerException को विधि-संदर्भ का उपयोग करके फेंक दिया गया है लेकिन लैम्ब्डा अभिव्यक्ति नहीं

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 बदलने के लिए एक विधि-संदर्भ का उपयोग करने के (यहां तक ​​कि इंटेलीजे पता चलता है कि):

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) 

कोई व्याख्या कर सकते हैं यहाँ क्या चल रहा है?

+0

मैंने अपने स्वयं को ग्रहण में चेक किया है और यह ठीक काम करता है। Ideone.com एनपीई को भी –

+1

फेंक देता है इसे जांचें: https://ideone.com/nPvWex – FaNaJ

+2

लैम्बडास का मूल्यांकन होने पर मूल्यांकन किया जाता है, घोषित किए जाने पर फ़ंक्शन संदर्भों का मूल्यांकन किया जाता है। – njzk2

उत्तर

44

यह व्यवहार विधि-संदर्भों और लैम्ब्डा अभिव्यक्तियों की मूल्यांकन प्रक्रिया के बीच एक सूक्ष्म अंतर पर निर्भर करता है।

JLS Run-Time Evaluation of Method References से:

पहला, अगर विधि संदर्भ अभिव्यक्ति एक ExpressionName या एक प्राथमिक साथ शुरू होता है, इस उपसूचक मूल्यांकन किया जाता है। यदि उप-संपीड़न null पर मूल्यांकन करता है, तो NullPointerException उठाया जाता है, और विधि संदर्भ अभिव्यक्ति अचानक पूर्ण हो जाती है।

निम्नलिखित कोड के साथ

:

Thread t = new Thread(s::toLowerCase); // <-- s is null, NullPointerException thrown here 
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!")); 

अभिव्यक्ति snull लिए मूल्यांकन किया जाता है और एक अपवाद वास्तव में फेंक दिया जाता है जब कि विधि-संदर्भ मूल्यांकन किया जाता है। हालांकि, उस समय, कोई अपवाद हैंडलर संलग्न नहीं था, क्योंकि इस कोड को बाद में निष्पादित किया जाएगा।

यह लैम्ब्डा अभिव्यक्ति के मामले में नहीं होता है, क्योंकि लैम्बडा का मूल्यांकन उसके शरीर के बिना किया जाएगा। Run-Time Evaluation of Lambda Expressions से:

लैम्ब्डा अभिव्यक्ति का मूल्यांकन लैम्ब्डा शरीर के निष्पादन से अलग है।

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

यहां तक ​​कि अगर snull है, लैम्ब्डा अभिव्यक्ति सही ढंग से बनाया जाएगा। फिर अपवाद हैंडलर संलग्न किया जाएगा, थ्रेड शुरू हो जाएगा, अपवाद फेंक देगा, जो हैंडलर द्वारा पकड़ा जाएगा।


एक नोट के रूप में, ऐसा लगता है ग्रहण Mars.2 इस बारे में एक छोटे से बग है: भी विधि-संदर्भ के साथ, यह अपवाद संचालक invokes। ग्रहण NullPointerExceptions::toLowerCase पर फेंक नहीं रहा है, जब इसे अपवाद हैंडलर जोड़ा गया था, इस प्रकार बाद में अपवाद को परिभाषित करना चाहिए।

+4

इसे पढ़ने के लिए एक और सैद्धांतिक तरीका है: अभिव्यक्तियों का मूल्यांकन "सामान्य रूपों" (यानी मान) के लिए किया जाता है। ऐसा इसलिए होता है कि * लैम्बडास * * सामान्य रूप * शरीर को तब तक मूल्यांकन नहीं करते जब तक कि इसे बुलाया न जाए (जिसे कमजोर सिर सामान्य रूप डब्ल्यूएचएनएफ भी कहा जाता है)। इसका अर्थ यह है कि "त्रुटि" मान "() -> त्रुटि" से अलग है क्योंकि बाद वाला फ़ंक्शन कॉल करते समय केवल त्रुटि उत्पन्न करता है। इस चाल का प्रयोग उत्सुक भाषाओं में फिक्सपॉइंट संयोजकों को लिखने के लिए भी किया जाता है: आप आलसी संयोजक 'फिक्स एफ = एफ (फिक्स एफ)' लेते हैं और आलसी 'फिक्स एफ = एक्स -> एफ (फिक्स एफ) एक्स' पेश करने के लिए लैम्ब्डा अबास्ट्रक्शन जोड़ें। । – Bakuriu

+4

यह भी देखें ["System.out :: println'" के लिए समकक्ष लैम्ब्डा अभिव्यक्ति क्या है (] http://stackoverflow.com/a/28025717/2711488) – Holger

+12

अपवाद-अपवाद हैंडलर से पहले अपवाद नहीं होता है सेट है, अपवाद 't' के बजाय मुख्य धागे में होता है। – Holger

6

वाह। आपने कुछ दिलचस्प खुलासा किया है।हमें निम्नलिखित पर एक नज़र लेते हैं:

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 पैरामीटर पर काम करता है: एक s है और दूसरा कुछ लोकेल है। यदि snull है, तो यह फ़ंक्शन निर्माण 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 के अंदर से। यही कारण है कि आपका अपवाद हैंडलर निष्पादित नहीं किया गया है।

+3

"' s :: toLowerCase' के आमंत्रण का नतीजा "को स्पष्टीकरण की आवश्यकता है। फ़ंक्शन इंस्टेंस का निर्माण एक आमंत्रण नहीं है। – Holger

संबंधित मुद्दे