Java Language
जावा नुकसान - नल और NullPointerException
खोज…
टिप्पणियों
मान null
एक फ़ील्ड का एक अनैतिक मूल्य के लिए डिफ़ॉल्ट मान है जिसका प्रकार एक संदर्भ प्रकार है।
NullPointerException
(या NPE) अपवाद है जिसे तब फेंका जाता है जब आप null
ऑब्जेक्ट संदर्भ पर अनुचित संचालन करने का प्रयास करते हैं। ऐसे कार्यों में शामिल हैं:
-
null
लक्ष्य ऑब्जेक्ट पर इंस्टेंस विधि को कॉल करना, - एक
null
लक्ष्य वस्तु के एक क्षेत्र तक पहुँचने, -
null
वस्तु को अनुक्रमित करने या उसकी लंबाई तक पहुँचने का प्रयास करना, - एक
synchronized
ब्लॉक में म्यूटेक्स के रूप में एकnull
वस्तु संदर्भ का उपयोग करना, - एक
null
वस्तु संदर्भ डालना, - एक
null
वस्तु संदर्भ unboxing, और - एक
null
वस्तु संदर्भ फेंकना।
एनपीई के लिए सबसे आम मूल कारण:
- एक संदर्भ प्रकार के साथ किसी क्षेत्र को आरम्भ करने की भूल करना,
- किसी संदर्भ प्रकार के सरणी के तत्वों को आरंभीकृत करना भूल जाना, या
- कुछ निश्चित परिस्थितियों में
null
लौटने के रूप में निर्दिष्ट कुछ एपीआई विधियों के परिणामों का परीक्षण नहीं कर रहा है।
आमतौर पर उपयोग किए जाने वाले तरीकों के उदाहरण जो null
हैं:
-
Map
एपीआई मेंget(key)
विधि एकnull
लौटाएगा यदि आप इसे एक कुंजी के साथ कहते हैं जिसमें मैपिंग नहीं है। -
getResource(path)
औरgetResourceAsStream(path)
मेथड्सClassLoader
औरClass
APIs यदि संसाधन नहीं मिल पा रहा है, तोnull
हो जाएगा। - यदि संदर्भ कलेक्टर ने संदर्भ को मंजूरी दे दी है, तो
Reference
API मेंget()
विधिnull
हो जाएगी। - यदि आप एक गैर-मौजूद अनुरोध पैरामीटर, सत्र या सत्र विशेषता और इसी तरह लाने का प्रयास करते हैं, तो जावा ईई सर्वलेट एपीआई में विभिन्न
getXxxx
तरीकेnull
हो जाएंगे।
अवांछित एनपीई से बचने के लिए रणनीतियाँ हैं, जैसे कि स्पष्ट रूप से null
लिए परीक्षण या "योदा नोटेशन" का उपयोग करना, लेकिन इन रणनीतियों में अक्सर आपके कोड में समस्याओं को छिपाने का अवांछनीय परिणाम होता है जो वास्तव में ठीक होना चाहिए।
नुकसान - आदिम आवरणों के अनावश्यक उपयोग से NullPointerException हो सकती है
कभी-कभी, प्रोग्रामर जो नए जावा हैं, वे आदिम प्रकार और आवरणों का परस्पर उपयोग करेंगे। इससे समस्याएं हो सकती हैं। इस उदाहरण पर विचार करें:
public class MyRecord {
public int a, b;
public Integer c, d;
}
...
MyRecord record = new MyRecord();
record.a = 1; // OK
record.b = record.b + 1; // OK
record.c = 1; // OK
record.d = record.d + 1; // throws a NullPointerException
हमारा MyRecord
वर्ग 1 अपने क्षेत्रों पर मूल्यों को आरंभ करने के लिए डिफ़ॉल्ट आरंभीकरण पर निर्भर करता है। इस प्रकार, जब हम एक new
रिकॉर्ड करते हैं, तो a
और b
फ़ील्ड शून्य पर सेट हो जाएंगे, और c
और d
फ़ील्ड null
सेट हो जाएंगे।
जब हम डिफ़ॉल्ट आरंभीकृत फ़ील्ड्स का उपयोग करने का प्रयास करते हैं, तो हम देखते हैं कि int
फ़ील्ड्स हर समय काम करती हैं, लेकिन Integer
फ़ील्ड कुछ मामलों में काम करते हैं और अन्य नहीं। विशेष रूप से, उस मामले में जो विफल होता है ( d
साथ), ऐसा क्या होता है कि दाएं हाथ की तरफ अभिव्यक्ति एक null
संदर्भ को अनबॉक्स करने का प्रयास करती है, और यही कारण है कि NullPointerException
को फेंक दिया जाता है।
इसे देखने के कुछ तरीके हैं:
यदि फ़ील्ड
c
औरd
को आदिम आवरण होने की आवश्यकता है, तो या तो हमें डिफ़ॉल्ट आरंभीकरण पर निर्भर नहीं होना चाहिए, या हमेंnull
लिए परीक्षण करना चाहिए। पूर्व के लिए सही दृष्टिकोण है जब तक किnull
अवस्था में खेतों के लिए कोई निश्चित अर्थ नहीं है।यदि खेतों को आदिम आवरण होने की आवश्यकता नहीं है, तो यह उन्हें आदिम आवरण बनाने के लिए एक गलती है। इस समस्या के अलावा, आदिम आवरणों में आदिम प्रकारों के सापेक्ष अतिरिक्त ओवरहेड्स होते हैं।
जब तक आपको वास्तव में ज़रूरत न हो तब तक सबक आदिम आवरण प्रकारों का उपयोग नहीं करना है।
1 - यह क्लास अच्छी कोडिंग प्रैक्टिस का उदाहरण नहीं है। उदाहरण के लिए, एक अच्छी तरह से डिजाइन वर्ग में सार्वजनिक क्षेत्र नहीं होंगे। हालाँकि, यह इस उदाहरण की बात नहीं है।
ख़तरा - रिक्त सरणी या संग्रह का प्रतिनिधित्व करने के लिए अशक्त का उपयोग करना
कुछ प्रोग्रामर सोचते हैं कि खाली सरणी या संग्रह का प्रतिनिधित्व करने के लिए null
का उपयोग करके अंतरिक्ष को बचाना एक अच्छा विचार है। हालांकि यह सच है कि आप अंतरिक्ष की एक छोटी राशि बचा सकते हैं, फ़्लिपसाइड यह है कि यह आपके कोड को अधिक जटिल बनाता है, और अधिक नाजुक। किसी सरणी के लिए विधि के इन दो संस्करणों की तुलना करें:
पहला संस्करण है कि आप सामान्य रूप से विधि को कैसे कोड करेंगे:
/**
* Sum the values in an array of integers.
* @arg values the array to be summed
* @return the sum
**/
public int sum(int[] values) {
int sum = 0;
for (int value : values) {
sum += value;
}
return sum;
}
दूसरा संस्करण यह है कि यदि आपको खाली सरणी का प्रतिनिधित्व करने के लिए null
का उपयोग करने की आदत है, तो आपको विधि को कोड करने की आवश्यकता है।
/**
* Sum the values in an array of integers.
* @arg values the array to be summed, or null.
* @return the sum, or zero if the array is null.
**/
public int sum(int[] values) {
int sum = 0;
if (values != null) {
for (int value : values) {
sum += value;
}
}
return sum;
}
जैसा कि आप देख सकते हैं, कोड थोड़ा अधिक जटिल है। इस तरह से null
उपयोग करने के निर्णय के लिए यह सीधे जिम्मेदार है।
अब विचार करें कि क्या यह सरणी जो एक null
हो सकती है, बहुत सारे स्थानों पर उपयोग की जाती है। प्रत्येक स्थान पर जहां आप इसका उपयोग करते हैं, आपको यह विचार करने की आवश्यकता है कि क्या आपको null
लिए परीक्षण करने की आवश्यकता है। यदि आपको एक null
परीक्षा याद आती है, जिसमें आपको होने की आवश्यकता है, तो आप NullPointerException
जोखिम में डालते हैं। इसलिए, इस तरह से null
उपयोग करने की रणनीति आपके आवेदन को अधिक नाजुक बनाती है; प्रोग्रामर त्रुटियों के परिणामों के लिए अधिक असुरक्षित है।
यहाँ सबक खाली सरणियों और खाली सूचियों का उपयोग करना है जब कि आपका मतलब है।
int[] values = new int[0]; // always empty
List<Integer> list = new ArrayList(); // initially empty
List<Integer> list = Collections.emptyList(); // always empty
अंतरिक्ष ओवरहेड छोटा है, और इसे कम करने के अन्य तरीके हैं यदि यह करने के लिए एक सार्थक चीज है।
नुकसान - "अच्छा बनाना" अप्रत्याशित नल
StackOverflow पर, हम अक्सर उत्तर में इस तरह कोड देखते हैं:
public String joinStrings(String a, String b) {
if (a == null) {
a = "";
}
if (b == null) {
b = "";
}
return a + ": " + b;
}
अक्सर, यह एक मुखरता के साथ होता है जो NullPointerException
से बचने के लिए इस तरह null
लिए परीक्षण करने के लिए "सर्वोत्तम अभ्यास" है।
क्या यह सबसे अच्छा अभ्यास है? संक्षेप में: नहीं।
कुछ अंतर्निहित धारणाएं हैं, joinStrings
हमें यह कहने से पहले सवाल करने की जरूरत है कि क्या हमारे joinStrings
में ऐसा करना एक अच्छा विचार है:
"ए" या "बी" के अशक्त होने का क्या मतलब है?
एक String
मान शून्य या अधिक वर्ण हो सकता है, इसलिए हमारे पास पहले से ही एक खाली स्ट्रिंग का प्रतिनिधित्व करने का एक तरीका है। क्या null
मतलब कुछ अलग है ""
? यदि नहीं, तो खाली स्ट्रिंग का प्रतिनिधित्व करने के दो तरीके होना समस्याग्रस्त है।
क्या अशक्त एक असिंचित चर से आया था?
null
एक असिंचित क्षेत्र, या एक असिंचित सरणी तत्व से आ सकता है। मूल्य डिजाइन द्वारा, या दुर्घटना से निर्विवाद हो सकता है। यदि यह दुर्घटना से होता है तो यह एक बग है।
क्या अशक्त "नहीं जानता" या "लापता मूल्य" का प्रतिनिधित्व करता है?
कभी-कभी एक null
वास्तविक अर्थ हो सकता है; जैसे कि एक चर का वास्तविक मूल्य अज्ञात या अनुपलब्ध या "वैकल्पिक" है। जावा 8 में, Optional
वर्ग इसे व्यक्त करने का एक बेहतर तरीका प्रदान करता है।
यदि यह एक बग (या एक डिज़ाइन त्रुटि) है तो क्या हमें "अच्छा बनाना चाहिए"?
कोड की एक व्याख्या यह है कि हम इसके स्थान पर एक खाली स्ट्रिंग का उपयोग करके एक अप्रत्याशित null
को "अच्छा बना रहे हैं"। क्या सही रणनीति है? क्या NullPointerException
होने देना बेहतर होगा, और फिर अपवाद को स्टैक के ऊपर से पकड़कर बग के रूप में लॉग इन करें?
"अच्छा बनाने" के साथ समस्या यह है कि यह या तो समस्या को छिपाने के लिए उत्तरदायी है, या इसका निदान करना कठिन है।
क्या यह कोड गुणवत्ता के लिए कुशल / अच्छा है?
यदि "मेक गुड" दृष्टिकोण का लगातार उपयोग किया जाता है, तो आपके कोड में बहुत सारे "रक्षात्मक" अशक्त परीक्षण शामिल होंगे। यह पढ़ने के लिए इसे लंबा और कठिन बनाता जा रहा है। इसके अलावा, इस परीक्षण के सभी और "अच्छा बनाने" आपके आवेदन के प्रदर्शन पर प्रभाव के लिए उत्तरदायी है।
संक्षेप में
यदि null
एक सार्थक मूल्य है, तो null
मामले के लिए परीक्षण सही दृष्टिकोण है। कोरोलरी यह है कि यदि कोई null
मान सार्थक है, तो यह स्पष्ट रूप से किसी भी तरीके के javadocs में दस्तावेज होना चाहिए जो null
मान को स्वीकार करते हैं या इसे वापस करते हैं।
अन्यथा, यह एक बेहतर विचार एक अप्रत्याशित इलाज के लिए null
एक प्रोग्रामिंग त्रुटि के रूप में, और NullPointerException
हो ताकि डेवलपर पता करने के लिए वहाँ कोड में एक समस्या है हो जाता है।
नुकसान - एक अपवाद को फेंकने के बजाय अशक्त लौटना
कुछ जावा प्रोग्रामर अपवादों को फेंकने या प्रचार करने के लिए एक सामान्य विरोधाभास रखते हैं। यह निम्नलिखित की तरह कोड की ओर जाता है:
public Reader getReader(String pathname) {
try {
return new BufferedReader(FileReader(pathname));
} catch (IOException ex) {
System.out.println("Open failed: " + ex.getMessage());
return null;
}
}
तो इसमें समस्या क्या है?
समस्या यह है कि getReader
एक विशेष मान के रूप में एक null
वापसी कर रहा है ताकि यह इंगित किया जा सके कि Reader
को खोला नहीं जा सका। अब दिए गए मान को यह देखने के लिए परीक्षण करने की आवश्यकता है कि क्या यह उपयोग करने से पहले null
है। यदि परीक्षण छोड़ दिया जाता है, तो परिणाम NullPointerException
।
यहाँ वास्तव में तीन समस्याएं हैं:
-
IOException
को बहुत जल्द पकड़ा गया था। - इस कोड की संरचना का मतलब है कि एक संसाधन को लीक करने का जोखिम है।
- एक
null
का उपयोग किया गया था, क्योंकि कोई "वास्तविक"Reader
वापस आने के लिए उपलब्ध नहीं था।
वास्तव में, यह मानते हुए कि अपवाद को इस तरह जल्दी पकड़ने की आवश्यकता थी, null
लौटने के लिए कुछ विकल्प थे:
-
NullReader
वर्ग को लागू करना संभव होगा; उदाहरण के लिए, जहां एपीआई का संचालन व्यवहार करता है जैसे कि पाठक "फाइल के अंत" स्थिति में पहले से ही था। - Java 8 के साथ,
getReader
को एकOptional<Reader>
रूप में लौटाने की घोषणा करना संभव होगा।
नुकसान - अगर I / O स्ट्रीम को बंद करते समय इसे इनिशियलाइज़ नहीं किया गया है, तो भी चेक न करें
मेमोरी लीक को रोकने के लिए, किसी को इनपुट स्ट्रीम या आउटपुट स्ट्रीम को बंद नहीं करना चाहिए, जिसका काम किया जाता है। यह आमतौर पर एक try
साथ किया जाता है - catch
- finally
में catch
भाग के बिना स्टेटमेंट:
void writeNullBytesToAFile(int count, String filename) throws IOException {
FileOutputStream out = null;
try {
out = new FileOutputStream(filename);
for(; count > 0; count--)
out.write(0);
} finally {
out.close();
}
}
जबकि उपरोक्त कोड निर्दोष दिख सकता है, इसमें एक दोष है जो डिबगिंग को असंभव बना सकता है। लाइन जहां तो out
(आरंभ नहीं हो जाता out = new FileOutputStream(filename)
) एक अपवाद फेंकता है, तो out
हो जाएगा null
जब out.close()
, निष्पादित किया जाता है एक बुरा है, जिसके परिणामस्वरूप NullPointerException
!
इसे रोकने के लिए, यह सुनिश्चित करने के लिए कि इसे बंद करने की कोशिश करने से पहले धारा null
नहीं है।
void writeNullBytesToAFile(int count, String filename) throws IOException {
FileOutputStream out = null;
try {
out = new FileOutputStream(filename);
for(; count > 0; count--)
out.write(0);
} finally {
if (out != null)
out.close();
}
}
इससे भी बेहतर तरीका है, try
-संसाधनों के बाद से, यह स्वतः 0 के प्रायिकता के साथ स्ट्रीम को बंद कर देगा क्योंकि NPE को finally
ब्लॉक की आवश्यकता के बिना फेंकने के लिए।
void writeNullBytesToAFile(int count, String filename) throws IOException {
try (FileOutputStream out = new FileOutputStream(filename)) {
for(; count > 0; count--)
out.write(0);
}
}
नुकसान - NullPointerException से बचने के लिए "Yoda संकेतन" का उपयोग करना
StackOverflow पर पोस्ट किए गए बहुत सारे उदाहरण कोड में इस तरह के स्निपेट शामिल हैं:
if ("A".equals(someString)) {
// do something
}
यह उस मामले में एक संभव NullPointerException
को "रोकना" या "टालना" करता है जो कुछ someString
null
। इसके अलावा, यह तर्क है कि
"A".equals(someString)
से बेहतर है:
someString != null && someString.equals("A")
(यह अधिक संक्षिप्त है, और कुछ परिस्थितियों में यह अधिक कुशल हो सकता है। हालांकि, जैसा कि हम नीचे तर्क देते हैं, संक्षिप्तता एक नकारात्मक हो सकती है।)
हालाँकि, असली नुकसान Yoda परीक्षण का उपयोग NullPointerExceptions
को आदत के मामले से बचने के लिए किया जाता है।
जब आप "A".equals(someString)
आप वास्तव में "अच्छा बना रहे हैं" उस मामले में जहां कुछ someString
null
होता है। लेकिन एक अन्य उदाहरण के रूप में ( ख़तरा - "अच्छा बनाना" अप्रत्याशित नल ) बताते हैं, "अच्छा" null
मान कई कारणों से हानिकारक हो सकता है।
इसका मतलब है कि योदा की स्थिति "सबसे अच्छा अभ्यास" 1 नहीं हैं। जब तक null
होने की उम्मीद है, तब तक बेहतर है कि NullPointerException
ऐसा करने दिया जाए ताकि आपको एक इकाई परीक्षण विफलता (या बग रिपोर्ट) मिल सके। यह आपको उस बग को खोजने और ठीक करने की अनुमति देता है जिससे अप्रत्याशित / अवांछित null
दिखाई देता है।
योदा की स्थितियों का उपयोग केवल उन मामलों में किया जाना चाहिए जहां null
होने की उम्मीद है क्योंकि आप जिस वस्तु का परीक्षण कर रहे हैं वह एक एपीआई से आया है जिसे एक null
लौटाने के रूप में प्रलेखित किया गया है। और यकीनन, परीक्षण को व्यक्त करने वाले कम सुंदर तरीकों में से किसी एक का उपयोग करना बेहतर हो सकता है क्योंकि यह आपके कोड की समीक्षा करने वाले व्यक्ति को null
परीक्षा को उजागर करने में मदद करता है।
1 - विकिपीडिया के अनुसार: "सर्वश्रेष्ठ कोडिंग प्रथाएं अनौपचारिक नियमों का एक सेट है जो सॉफ्टवेयर विकास समुदाय ने समय के साथ सीखा है जो सॉफ्टवेयर की गुणवत्ता में सुधार करने में मदद कर सकता है।" । योदा अंकन का उपयोग करने से यह हासिल नहीं होता है। बहुत सारी स्थितियों में, यह कोड को बदतर बनाता है।