किसी को Objects.requireNonNull () का उपयोग क्यों करना चाहिए?


214

मैंने नोट किया है कि Oracle JDK उपयोग में कई जावा 8 विधियाँ Objects.requireNonNull(), जो NullPointerExceptionदी गई वस्तु (तर्क) हैं , तो आंतरिक रूप से फेंकता है null

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

लेकिन NullPointerExceptionवैसे भी फेंक दिया जाएगा अगर किसी nullवस्तु को गिरवी रखा जाए। तो, किसी को यह अतिरिक्त अशक्त जांच और फेंकना क्यों चाहिए NullPointerException?

एक स्पष्ट उत्तर (या लाभ) यह है कि यह कोड को अधिक पठनीय बनाता है और मैं सहमत हूं। मैं Objects.requireNonNull()विधि की शुरुआत में उपयोग करने के लिए किसी भी अन्य कारणों को जानने के लिए उत्सुक हूं ।




1
जब आप इकाई परीक्षण लिख रहे हों, तब तर्क की जाँच करने का तरीका आपको धोखा देता है। वसंत की भी ऐसी उपयोगिताएँ हैं (देखें docs.spring.io/spring/docs/current/javadoc-api/org/… )। यदि आपके पास "यदि" है और उच्च इकाई परीक्षण कवरेज करना चाहते हैं, तो आपको दोनों शाखाओं को कवर करना होगा: जब शर्त पूरी हो जाती है, और जब शर्त पूरी नहीं होती है। यदि आप उपयोग करते हैं Objects.requireNonNull, तो आपके कोड में कोई ब्रांचिंग नहीं है, ताकि यूनिट टेस्ट के एक भी पास से आपको 100% कवरेज मिल जाए :-)
xorcus

जवाबों:


214

क्योंकि आप ऐसा करके चीजों को स्पष्ट कर सकते हैं । पसंद:

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }

या कम:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");

अब आप जानते हैं :

  • जब कोई Foo ऑब्जेक्ट सफलतापूर्वक बनाया गया थाnew()
  • फिर इसके बार क्षेत्र को गैर-शून्य होने की गारंटी दी जाती है।

उस से तुलना करें: आप आज एक फू ऑब्जेक्ट बनाते हैं, और कल आप उस क्षेत्र का उपयोग करते हैं और फेंकता है। सबसे अधिक संभावना है, तुम कल पता नहीं होगा क्यों कि संदर्भ अशक्त था कल जब यह निर्माता को पारित कर दिया गया!

दूसरे शब्दों में: आने वाले संदर्भों की जांच करने के लिए स्पष्ट रूप से इस पद्धति का उपयोग करके आप उस समय में बिंदु को नियंत्रित कर सकते हैं जब अपवाद फेंक दिया जाएगा। और ज्यादातर समय, आप जितनी जल्दी हो सके असफल होना चाहते हैं !

प्रमुख लाभ हैं:

  • जैसा कि कहा गया है, नियंत्रित व्यवहार
  • आसान डिबगिंग - क्योंकि आप ऑब्जेक्ट निर्माण के संदर्भ में फेंक देते हैं। उस समय जहां आपके पास एक निश्चित मौका होता है कि आपके लॉग / निशान आपको बताते हैं कि क्या गलत हुआ!
  • और जैसा कि ऊपर दिखाया गया है: इस विचार की असली शक्ति अंतिम क्षेत्रों के साथ मिलकर प्रकट होती है । क्योंकि अब आपकी कक्षा में कोई भी अन्य कोड सुरक्षित रूप से मान सकता है कि barयह अशक्त नहीं है - और इस प्रकार आपको if (bar == null)अन्य स्थानों पर किसी भी चेक की आवश्यकता नहीं है !

23
आप अपने कोड को लिखकर और अधिक कॉम्पैक्ट बना सकते हैंthis.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman

3
@KirillRakhman टीचिंग घोस्टकैट कुछ अच्छा है जिसके बारे में मुझे नहीं पता था -> घोस्टकट अपविट लॉटरी में टिकट जीतना।
घोस्टकट

1
मुझे लगता है कि टिप्पणी # 1 यहाँ का वास्तविक लाभ है Objects.requireNonNull(bar, "bar must not be null");। इसके लिए धन्यवाद।
कावु

1
कौन सा पहले था, और क्यों "भाषा" लोगों को कुछ पुस्तकालय के लेखकों का पालन करना चाहिए ?! अमरूद जावा लैंग व्यवहार का पालन क्यों नहीं करता है ?!
घोस्टकट

1
this.bar = Objects.requireNonNull(bar, "bar must not be null");दो या अधिक चर एक ही विधि में स्थापित कर रहे हैं, तो निर्माताओं में ठीक है, लेकिन अन्य तरीकों में संभावित खतरनाक है। उदाहरण: talkwards.com/2018/11/03/…
Erk

100

फेल-तेज

कोड जितनी जल्दी हो सके दुर्घटनाग्रस्त हो जाना चाहिए। यह काम का आधा हिस्सा नहीं करना चाहिए और फिर अशक्तता और दुर्घटना को रोकना चाहिए, तभी कुछ काम पूरा हो जाएगा, जिससे सिस्टम अमान्य स्थिति में हो सकता है।

इसे आमतौर पर "असफल जल्दी" या "असफल-तेज" कहा जाता है


40

लेकिन NullPointerException को वैसे भी फेंक दिया जाएगा अगर कोई अशक्त वस्तु dereferenced है। तो, किसी को यह अतिरिक्त अशक्त जाँच क्यों करनी चाहिए और NullPointerException को फेंक देना चाहिए?

इसका मतलब है कि आप समस्या का तुरंत और मज़बूती से पता लगा सकते हैं ।

विचार करें:

  • संदर्भ का उपयोग बाद में विधि में नहीं किया जा सकता है, क्योंकि आपके कोड ने पहले ही कुछ साइड-इफेक्ट किए हैं
  • हो सकता है कि संदर्भ को इस पद्धति में बिल्कुल भी सीमित न किया गया हो
    • यह पूरी तरह से अलग कोड के लिए पारित किया जा सकता है (यानी कारण और त्रुटि कोड स्थान में दूर हैं)
    • यह बहुत बाद में इस्तेमाल किया जा सकता है (यानी कारण और त्रुटि समय के अलावा दूर हैं)
  • यह प्रयोग किया जाता कहीं हो सकता है कि एक अशक्त संदर्भ है मान्य है, लेकिन एक अनपेक्षित प्रभाव पड़ता है

.NET इसे अलग करता है NullReferenceException("आपने एक शून्य मान घटाया") से ArgumentNullException("आप एक तर्क के रूप में अशक्त में पारित नहीं होना चाहिए था - और यह इस पैरामीटर के लिए था )। काश जावा भी यही काम करता, लेकिन साथ भी। सिर्फ एक NullPointerException, यह अभी भी बहुत आसान ठीक कोड के लिए अगर त्रुटि जल्द से जल्द बिंदु है जिस पर यह पता लगाया जा सकता पर फेंक दिया है।


12

requireNonNull()एक विधि में पहले कथनों के रूप में उपयोग करने से अभी पहचान करने की अनुमति मिलती है।
स्टैकट्रेस स्पष्ट रूप से इंगित करता है कि अपवाद को विधि के प्रवेश के रूप में जल्द ही फेंक दिया गया था क्योंकि कॉलर आवश्यकताओं / अनुबंध का सम्मान नहीं करता था। nullकिसी अन्य विधि से किसी वस्तु को पास करना वास्तव में एक समय में एक अपवाद को उत्तेजित कर सकता है लेकिन समस्या का कारण समझने के लिए अधिक जटिल हो सकता है क्योंकि अपवाद को उस nullवस्तु पर एक विशिष्ट आह्वान में फेंक दिया जाएगा जो बहुत आगे हो सकता है।


यहां एक ठोस और वास्तविक उदाहरण है, जो बताता है कि क्यों हमें सामान्य रूप से तेजी से विफल होने और अधिक विशेष रूप से उपयोग Object.requireNonNull()करने के लिए या किसी भी तरह से नहीं होने वाले मापदंडों पर कोई अशक्त जांच करने के लिए किसी भी तरह से विफल होना पड़ता है null

मान लीजिए कि एक Dictionaryवर्ग जो रचना करता है LookupServiceऔर इसमें निहित शब्दों Listका Stringप्रतिनिधित्व करता है। इन क्षेत्रों को डिज़ाइन नहीं किया गया है nullऔर इनमें से एक को Dictionary निर्माणकर्ता में पारित किया गया है ।

अब मान लें कि विधि प्रविष्टि (यहां निर्माणकर्ता है) में Dictionaryबिना nullजांच के "खराब" कार्यान्वयन :

public class Dictionary {

    private final List<String> words;
    private final LookupService lookupService;

    public Dictionary(List<String> words) {
        this.words = this.words;
        this.lookupService = new LookupService(words);
    }

    public boolean isFirstElement(String userData) {
        return lookupService.isFirstElement(userData);
    }        
}


public class LookupService {

    List<String> words;

    public LookupService(List<String> words) {
        this.words = words;
    }

    public boolean isFirstElement(String userData) {
        return words.get(0).contains(userData);
    }
}

अब, पैरामीटर के लिए संदर्भ के Dictionaryसाथ निर्माता को आमंत्रित करें :nullwords

Dictionary dictionary = new Dictionary(null); 

// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");

JVM ने इस कथन पर NPE फेंका:

return words.get(0).contains(userData); 
थ्रेड में अपवाद "मुख्य" java.lang.NullPointerException
    LookupService.isFirstElement (LookupService.java) पर
    Dictionary.isFirstElement (Dictionary.java:15) पर
    Dictionary.main पर (Dictionary.java:22)

अपवाद LookupServiceकक्षा में शुरू हो जाता है जबकि इसका मूल पहले ( Dictionaryनिर्माणकर्ता) है। यह समग्र मुद्दे के विश्लेषण को बहुत कम स्पष्ट करता है।
है words null? है words.get(0) null? दोनों? क्यों एक, दूसरे या शायद दोनों हैं null? क्या यह Dictionary(निर्माणकर्ता? आह्वान विधि?) में एक कोडिंग त्रुटि है ? क्या यह एक कोडिंग त्रुटि है LookupService? (कंस्ट्रक्टर? चालान विधि?)
अंत में, हमें त्रुटि मूल को खोजने के लिए अधिक कोड का निरीक्षण करना होगा और एक अधिक जटिल वर्ग में शायद डिबगर का उपयोग करने के लिए और अधिक आसानी से समझने के लिए कि यह क्या हुआ।
लेकिन एक साधारण बात (शून्य जांच की कमी) एक जटिल मुद्दा क्यों बन जाती है?
क्योंकि हमने निचले घटकों पर एक विशिष्ट घटक रिसाव पर प्रारंभिक बग / अभाव पहचान की अनुमति दी थी।
कल्पना करें कि LookupServiceकुछ डिबगिंग जानकारी के साथ एक स्थानीय सेवा नहीं बल्कि एक दूरस्थ सेवा या एक थर्ड पार्टी लाइब्रेरी थी या कल्पना करें कि आपके पास 2 परतें नहीं थीं लेकिन इससे पहले कि वस्तु का 4 या 5 परतों पर आक्रमण nullहो? समस्या का विश्लेषण करने के लिए अभी भी अधिक जटिल होगा।

तो एहसान करने का तरीका है:

public Dictionary(List<String> words) {
    this.words = Objects.requireNonNull(words);
    this.lookupService = new LookupService(words);
}

इस तरह से, कोई सिरदर्द नहीं: जैसे ही यह प्राप्त होता है, हमें अपवाद फेंक दिया जाता है:

// exception thrown early : in the constructor 
Dictionary dictionary = new Dictionary(null);

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
थ्रेड में अपवाद "मुख्य" java.lang.NullPointerException
    java.util.Objects.requireNonNull (Objects.java:203) पर
    com.Dictionary पर। (Dictionary.java:15)
    com.Dictionary.main पर (Dictionary.java:24)

ध्यान दें कि यहाँ मैंने एक निर्माता के साथ समस्या का वर्णन किया था, लेकिन एक विधि आह्वान में एक ही गैर-शून्य चेक बाधा हो सकती है।


वाह! महान व्याख्या।
जोनाथेस नेस्केमेंटो

2

एक साइड नोट के रूप में, यह तेजी से विफल हो जाता है इससे Object#requireNotNullपहले कि कुछ स्वयं जावेद वर्गों के अंदर java-9 से पहले थोड़ा अलग लागू किया गया था। मामला मान लीजिए:

 Consumer<String> consumer = System.out::println;

जावा -8 में यह (केवल प्रासंगिक भागों के रूप में) संकलित है

getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass

मूल रूप से एक ऑपरेशन के रूप में: yourReference.getClass- जो कि विफल हो जाता है अगर आपका रेफरेंस है null

Jdk-9 में चीजें बदल गई हैं जहां समान कोड संकलन करता है

getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull

या मूल रूप से Objects.requireNotNull (yourReference)


1

मूल उपयोग NullPointerExceptionतुरंत जाँच और फेंक रहा है।

एक ही आवश्यकता को पूरा करने के लिए एक बेहतर विकल्प (शॉर्टकट) है lombok द्वारा @NonNull एनोटेशन।


1
एनोटेशन ऑब्जेक्ट विधि के लिए एक प्रतिस्थापन नहीं है, वे एक साथ काम करते हैं। आप @ NonNull x = mayBeNull नहीं कह सकते, आप @ NonNull x = Objects.requireNonNull (mayBeNull, "समझ से बाहर!") कहेंगे।
बिल के

@BillK माफ करना, मुझे नहीं यू मिल
Supun Wijerathne

मैं सिर्फ यह कह रहा था कि नॉननुल एनोटेशन आवश्यकता के साथ काम करता है, यह कोई विकल्प नहीं है, लेकिन वे एक साथ काफी अच्छी तरह से काम करते हैं।
बिल के

विफल करने के लिए तेजी से प्रकृति को लागू करने के लिए, यह एक वैकल्पिक अधिकार है? Yh मैं सहमत हूँ कि इसके असाइनमेंट का कोई विकल्प नहीं है।
सुपून विजेरथने

मुझे लगता है कि मुझे कॉल करना होगा NNNNull () @ Nullable to @ NonNull से एक "रूपांतरण"। यदि आप एनोटेशन का उपयोग नहीं करते हैं, तो विधि वास्तव में बहुत दिलचस्प नहीं है (क्योंकि यह सब करता है एक एनपीई को उस कोड की तरह फेंक दिया जाता है जो इसे बचाता है) - हालांकि यह बहुत स्पष्ट रूप से आपके इरादे को दर्शाता है।
बिल के

1

जब आप किसी ऑब्जेक्ट के सदस्य तक पहुँचते हैं null, जो बाद में एक बिंदु पर होता है , तो नल पॉइंटर अपवाद को फेंक दिया जाता है। Objects.requireNonNull()तुरंत मूल्य की जाँच करता है और आगे बढ़ने के बिना तुरंत अपवाद फेंकता है।


0

मुझे लगता है कि इसका उपयोग कॉपी कंस्ट्रक्टर्स और डीआई जैसे कुछ अन्य मामलों में किया जाना चाहिए जिसका इनपुट पैरामीटर एक ऑब्जेक्ट है, आपको जांचना चाहिए कि क्या पैरामीटर अशक्त है। ऐसी परिस्थितियों में, आप इस स्थिर विधि का उपयोग आसानी से कर सकते हैं।


0

संकलक एक्सटेंशन के संदर्भ में, जो अशक्तता जाँच (जैसे: uber / NullAway ) को लागू करते हैं, Objects.requireNonNullउन अवसरों के लिए कुछ हद तक उपयोग किया जाना चाहिए जब आपके पास एक अशक्त क्षेत्र होता है जिसे आप जानते हैं कि आपके कोड में एक निश्चित बिंदु पर शून्य नहीं है।

इस तरह, दो मुख्य उपयोग हैं:

  • मान्यकरण

    • यहां पहले से ही अन्य प्रतिक्रियाओं द्वारा कवर किया गया है
    • ओवरहेड और संभावित एनपीई के साथ रनटाइम चेक
  • अशक्तता अंकन (@Nullable से @Nonnull में परिवर्तन)

    • संकलन-समय जाँच के पक्ष में रनटाइम जाँच का न्यूनतम उपयोग
    • केवल तब काम करता है जब एनोटेशन सही हो (कंपाइलर द्वारा लागू)

अशक्तता अंकन का उदाहरण उपयोग:

@Nullable
Foo getFoo(boolean getNull) { return getNull ? null : new Foo(); }

// Changes contract from Nullable to Nonnull without compiler error
@Nonnull Foo myFoo = Objects.requireNonNull(getFoo(false));

0

सभी सही उत्तरों के अलावा:

हम इसका उपयोग प्रतिक्रियाशील धाराओं में करते हैं। आमतौर पर परिणामी NullpointerExceptionधारा में मौजूद प्रदूषण के आधार पर अन्य अपवादों में लिपटे रहते हैं। इसलिए, हम बाद में आसानी से तय कर सकते हैं कि त्रुटि को कैसे संभालना है।

बस एक उदाहरण: आप कल्पना कीजिए

<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };

जहां से एक में parseAndValidateलपेटता है ।NullPointerExceptionrequireNonNullParsingException

अब आप तय कर सकते हैं, जैसे कि रिट्रीट कब करना है या नहीं:

...
.map(this::parseAndValidate)
.map(this::save)
.retry(Retry.<T>allBut(ParsingException.class))

जांच के बिना, NullPointerException saveविधि में ढल जाएगा , जिसके परिणामस्वरूप अंतहीन रिट्रीट होंगे। यहां तक ​​कि लायक, एक लंबी जीवित सदस्यता की कल्पना करते हुए, जोड़ना

.onErrorContinue(
    throwable -> throwable.getClass().equals(ParsingException.class),
    parsingExceptionConsumer()
)

अब RetryExhaustExceptionआपकी सदस्यता को मार देगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.