कोटलिन में, अशक्त मूल्यों से निपटने, उन्हें संदर्भित करने या परिवर्तित करने का मुहावरेदार तरीका क्या है


177

यदि मेरे पास एक अशक्त प्रकार है Xyz?, तो मैं इसे संदर्भित करना चाहता हूं या इसे एक अशक्त प्रकार में परिवर्तित करना चाहता हूं Xyz। कोटलिन में ऐसा करने का मुहावरेदार तरीका क्या है?

उदाहरण के लिए, यह कोड त्रुटि में है:

val something: Xyz? = createPossiblyNullXyz()
something.foo() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Xyz?"

लेकिन अगर मैं अशक्त जांच करता हूं तो इसकी अनुमति है, क्यों?

val something: Xyz? = createPossiblyNullXyz()
if (something != null) {
    something.foo() 
}

मैं चेक की nullआवश्यकता के बिना किसी मूल्य को कैसे बदलूं या व्यवहार करूं if, यह मानते हुए कि मुझे पता है कि यह वास्तव में कभी नहीं है null? उदाहरण के लिए, यहां मैं एक मानचित्र से एक मूल्य प्राप्त कर रहा हूं जिसे मैं गारंटी दे सकता हूं कि मौजूद है और का परिणाम get()नहीं है null। लेकिन मेरे पास एक त्रुटि है:

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")
something.toLong() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?"

विधि get()सोचती है कि यह संभव है कि आइटम गायब है और प्रकार लौटता है Int?। इसलिए, मूल्य के प्रकार को अशक्त न करने का सबसे अच्छा तरीका क्या है?

नोट: यह प्रश्न जानबूझकर लिखा गया है और लेखक द्वारा ( स्व-उत्तर वाले प्रश्न ) उत्तर दिया गया है , ताकि आमतौर पर पूछे जाने वाले कोटलिन विषयों के मुहावरेदार उत्तर एसओ में मौजूद हों। कोटलिन के अल्फ़ाज़ों के लिए लिखे गए कुछ वास्तव में पुराने उत्तरों को स्पष्ट करने के लिए, जो वर्तमान के कोटलिन के लिए सटीक नहीं हैं।

जवाबों:


296

सबसे पहले, आपको कोटलिन में नल सुरक्षा के बारे में सभी पढ़ना चाहिए जो मामलों को अच्छी तरह से कवर करता है।

कोटलिन में, आप यह सुनिश्चित नहीं होने के बिना एक अशक्त मान तक नहीं पहुँच सकते null( शर्तों में अशक्तता की जाँच करना ), या यह सुनिश्चित करना कि यह निश्चित रूप से निश्चित ऑपरेटरnull का उपयोग नहीं कर रहा !!है , इसे ?.सुरक्षित कॉल के साथ एक्सेस करना , या अंतिम रूप से ऐसा कुछ देना जो संभवतः nullहै ?:एल्विस ऑपरेटर का उपयोग करके डिफ़ॉल्ट मान ।

आपके प्रश्न में आपके 1 मामले के लिए आपके पास इनमें से किसी एक का उपयोग करने वाले कोड के इरादे के आधार पर विकल्प हैं, और सभी मुहावरेदार हैं, लेकिन अलग-अलग परिणाम हैं:

val something: Xyz? = createPossiblyNullXyz()

// access it as non-null asserting that with a sure call
val result1 = something!!.foo()

// access it only if it is not null using safe operator, 
// returning null otherwise
val result2 = something?.foo()

// access it only if it is not null using safe operator, 
// otherwise a default value using the elvis operator
val result3 = something?.foo() ?: differentValue

// null check it with `if` expression and then use the value, 
// similar to result3 but for more complex cases harder to do in one expression
val result4 = if (something != null) {
                   something.foo() 
              } else { 
                   ...
                   differentValue 
              }

// null check it with `if` statement doing a different action
if (something != null) { 
    something.foo() 
} else { 
    someOtherAction() 
}

"यह क्यों काम करता है जब अशक्त जाँच की जाती है" स्मार्ट कास्ट पर नीचे की पृष्ठभूमि की जानकारी पढ़ें ।

Mapयदि आप एक डेवलपर के रूप में सवाल में अपने 2 मामले के लिए , यदि आप एक डेवलपर के रूप में परिणाम कभी नहीं होने के बारे में सुनिश्चित कर रहे हैं, तो सुनिश्चित nullकरें !!कि ऑपरेटर का उपयोग एक जोर के रूप में करें:

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid

या किसी अन्य मामले में, जब मानचित्र COULD वापस आता है, लेकिन आप एक डिफ़ॉल्ट मान प्रदान कर सकते हैं, तो Mapअपने आप में एक getOrElseविधि है :

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.getOrElse("z") { 0 } // provide default value in lambda
something.toLong() // now valid

पृष्ठभूमि की जानकारी:

नोट: नीचे दिए गए उदाहरणों में मैं व्यवहार को स्पष्ट करने के लिए स्पष्ट प्रकारों का उपयोग कर रहा हूं। प्रकार के अनुमान के साथ, आमतौर पर प्रकार स्थानीय चर और निजी सदस्यों के लिए छोड़ा जा सकता है।

!!यकीन है कि ऑपरेटर के बारे में अधिक

!!ऑपरेटर का दावा है कि मूल्य नहीं है nullया एक एनपीई फेंकता है। इसका उपयोग उन मामलों में किया जाना चाहिए जहां डेवलपर गारंटी दे रहा है कि मूल्य कभी नहीं होगा null। इसे एक चतुर के रूप में एक मुखर के रूप में सोचो ।

val possibleXyz: Xyz? = ...
// assert it is not null, but if it is throw an exception:
val surelyXyz: Xyz = possibleXyz!! 
// same thing but access members after the assertion is made:
possibleXyz!!.foo()

और पढ़ें: !! निश्चित संचालक


nullचेकिंग और स्मार्ट कास्ट के बारे में अधिक

यदि आप nullचेक के साथ एक अशक्त प्रकार तक पहुंच की रक्षा करते हैं , तो कंपाइलर स्मार्ट हो जाएगा बयान के शरीर के भीतर मूल्य गैर-अशक्त होने के लिए। कुछ जटिल प्रवाह हैं जहां ऐसा नहीं हो सकता है, लेकिन सामान्य मामलों के लिए यह ठीक काम करता है।

val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
   // allowed to reference members:
   possiblyXyz.foo()
   // or also assign as non-nullable type:
   val surelyXyz: Xyz = possibleXyz
}

या यदि आप एक isअशक्त प्रकार के लिए एक जाँच करते हैं:

if (possibleXyz is Xyz) {
   // allowed to reference members:
   possiblyXyz.foo()
}

और 'जब' भावों के लिए भी वही सुरक्षित कास्ट है:

when (possibleXyz) {
    null -> doSomething()
    else -> possibleXyz.foo()
}

// or

when (possibleXyz) {
    is Xyz -> possibleXyz.foo()
    is Alpha -> possibleXyz.dominate()
    is Fish -> possibleXyz.swim() 
}

कुछ चीजें चर के बाद के उपयोग के लिए स्मार्ट कास्टnull को चेक की अनुमति नहीं देती हैं । ऊपर दिया गया उदाहरण एक स्थानीय चर का उपयोग करता है जो किसी भी तरह से अनुप्रयोग के प्रवाह में उत्परिवर्तित नहीं हो सकता था, चाहे या इस चर को एक में उत्परिवर्तित करने का कोई अवसर नहीं था । लेकिन, अन्य मामलों में जहां कंपाइलर प्रवाह विश्लेषण की गारंटी नहीं दे सकता है, यह एक त्रुटि होगी:valvarnull

var nullableInt: Int? = ...

public fun foo() {
    if (nullableInt != null) {
        // Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time"
        val nonNullableInt: Int = nullableInt
    }
}

चर nullableIntका जीवनचक्र पूरी तरह से दिखाई नहीं देता है और अन्य थ्रेड्स से असाइन किया जा सकता है, nullचेक को एक गैर-अशक्त मान में स्मार्ट कास्ट नहीं किया जा सकता है । वर्कअराउंड के लिए नीचे "सुरक्षित कॉल" विषय देखें।

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

class MyThing {
    val possibleXyz: Xyz? 
        get() { ... }
}

// now when referencing this class...

val thing = MyThing()
if (thing.possibleXyz != null) {
   // error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'p.x' is a property that has open or custom getter"
   thing.possiblyXyz.foo()
}

और पढ़ें: स्थितियों में अशक्त के लिए जाँच


?.सुरक्षित कॉल ऑपरेटर के बारे में अधिक

सुरक्षित कॉल ऑपरेटर शून्य हो जाता है यदि बाईं ओर का मान शून्य है, अन्यथा दाईं ओर अभिव्यक्ति का मूल्यांकन करना जारी रखता है।

val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()

एक अन्य उदाहरण जहां आप किसी सूची को पुनरावृत्त करना चाहते हैं, लेकिन केवल nullखाली और खाली न होने पर, फिर से सुरक्षित कॉल ऑपरेटर काम में आता है:

val things: List? = makeMeAListOrDont()
things?.forEach {
    // this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}

ऊपर दिए गए उदाहरणों में से एक में हमारे पास एक मामला था जहां हमने एक ifजांच की थी लेकिन मौका एक और धागा मूल्य को उत्परिवर्तित करता है और इसलिए कोई स्मार्ट कास्ट नहीं है । हम इसे letहल करने के लिए फ़ंक्शन के साथ सुरक्षित कॉल ऑपरेटर का उपयोग करने के लिए इस नमूने को बदल सकते हैं :

var possibleXyz: Xyz? = 1

public fun foo() {
    possibleXyz?.let { value ->
        // only called if not null, and the value is captured by the lambda
        val surelyXyz: Xyz = value
    }
}

और पढ़ें: सुरक्षित कॉल


?:एल्विस ऑपरेटर के बारे में अधिक

एल्विस ऑपरेटर आपको एक वैकल्पिक मूल्य प्रदान करने की अनुमति देता है जब ऑपरेटर के बाईं ओर एक अभिव्यक्ति है null:

val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()

इसके कुछ रचनात्मक उपयोग भी हैं, उदाहरण के लिए जब कुछ होता है तो एक अपवाद को फेंक दें null:

val currentUser = session.user ?: throw Http401Error("Unauthorized")

या एक समारोह से जल्दी लौटने के लिए:

fun foo(key: String): Int {
   val startingCode: String = codes.findKey(key) ?: return 0
   // ...
   return endingValue
}

और अधिक पढ़ें: एल्विस ऑपरेटर


संबंधित कार्यों के साथ अशक्त संचालक

Kotlin stdlib में उन कार्यों की एक श्रृंखला है जो ऊपर वर्णित ऑपरेटरों के साथ वास्तव में अच्छी तरह से काम करते हैं। उदाहरण के लिए:

// use ?.let() to change a not null value, and ?: to provide a default
val something = possibleNull?.let { it.transform() } ?: defaultSomething

// use ?.apply() to operate further on a value that is not null
possibleNull?.apply {
    func1()
    func2()
}

// use .takeIf or .takeUnless to turn a value null if it meets a predicate
val something = name.takeIf { it.isNotBlank() } ?: defaultName

val something = name.takeUnless { it.isBlank() } ?: defaultName

संबंधित विषय

कोटलिन में, अधिकांश एप्लिकेशन nullमूल्यों से बचने की कोशिश करते हैं, लेकिन यह हमेशा संभव नहीं होता है। और कभी null- कभी एकदम सही समझ में आता है। सोचने के लिए कुछ दिशानिर्देश:

  • कुछ मामलों में, यह विभिन्न रिटर्न प्रकारों को वारंट करता है जिसमें विधि कॉल की स्थिति और परिणाम सफल होने पर शामिल होता है। परिणाम जैसे पुस्तकालय आपको एक सफलता या विफलता परिणाम प्रकार देते हैं जो आपके कोड को भी शाखा दे सकते हैं। और कोटलिन के लिए प्रोमिस लाइब्रेरी, जिसे कोवंत कहा जाता है, वादों के रूप में ही करता है।

  • रिटर्न प्रकारों के लिए संग्रह हमेशा एक के बजाय एक खाली संग्रह लौटाते हैं null, जब तक कि आपको "मौजूद नहीं" के तीसरे राज्य की आवश्यकता होती है। कोटलिन के पास इन खाली मूल्यों को बनाने emptyList()याemptySet() बनाने के लिए सहायक कार्य हैं।

  • ऐसे तरीकों का उपयोग करते समय जो एक अशक्त मान लौटाते हैं जिसके लिए आपके पास एक डिफ़ॉल्ट या विकल्प है, एक डिफ़ॉल्ट मान प्रदान करने के लिए एल्विस ऑपरेटर का उपयोग करें। एक Mapउपयोग के मामले में getOrElse()जो Mapविधि के बजाय एक डिफ़ॉल्ट मान उत्पन्न करने की अनुमति देता है get()जो एक अशक्त मान लौटाता है। उसी के लिएgetOrPut()

  • जब जावा से तरीकों को ओवरराइड करना जहां कोटलिन जावा कोड की अशक्तता के बारे में निश्चित नहीं है, तो आप हमेशा ?अपने ओवरराइड से अशक्तता को छोड़ सकते हैं यदि आप सुनिश्चित हैं कि हस्ताक्षर और कार्यक्षमता क्या होनी चाहिए। इसलिए आपकी ओवरराइड विधि अधिक nullसुरक्षित है। कोटलिन में जावा इंटरफेस को लागू करने के लिए समान है, जो आप जानते हैं कि मान्य होने की अशक्तता को बदलें।

  • उन कार्यों को देखें जो पहले से ही मदद कर सकते हैं, जैसे कि String?.isNullOrEmpty()और String?.isNullOrBlank()जो कि एक अशक्त मूल्य पर सुरक्षित रूप से काम कर सकता है और वह कर सकता है जो आप उम्मीद करते हैं। वास्तव में, आप मानक पुस्तकालय में किसी भी अंतराल को भरने के लिए अपने स्वयं के एक्सटेंशन जोड़ सकते हैं।

  • मुखर पुस्तकालय में checkNotNull()और जैसे requireNotNull()मानक कार्य करता है ।

  • सहायक कार्य जैसे filterNotNull()कि संग्रह से नल हटाते हैं, या listOfNotNull()संभवतः nullमूल्य से शून्य या एकल आइटम सूची वापस करने के लिए ।

  • एक सुरक्षित ( अशक्त ) कास्ट ऑपरेटर है और साथ ही यह भी संभव नहीं है कि एक डाली गैर अशक्त प्रकार वापसी नल के लिए अनुमति देता है। लेकिन मेरे पास इसके लिए वैध उपयोग का मामला नहीं है जो ऊपर उल्लिखित अन्य विधियों द्वारा हल नहीं किया गया है।


1

पिछला उत्तर अनुसरण करने के लिए एक कठिन कार्य है, लेकिन यहां एक त्वरित और आसान तरीका है:

val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null")
something.foo() 

यदि यह वास्तव में कभी अशक्त नहीं है, तो अपवाद नहीं होगा, लेकिन यदि यह कभी भी हो तो आप देखेंगे कि क्या गलत हुआ।


12
वैल कुछ: Xyz = createPossablyNullXyz () !! एक NPE फेंक देंगे जब createPossiblyNullXyz () रिटर्न शून्य। यह सरल है और आपके द्वारा ज्ञात मूल्य से निपटने के लिए परंपराओं का पालन करना शून्य नहीं है
स्टीवन वाटरमैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.