प्रोग्रामर के लिए Kotlin Bootcamp 5.2: Generics

यह कोडलैब, प्रोग्रामर के लिए Kotlin बूटकैंप कोर्स का हिस्सा है. अगर कोडलैब को क्रम से पूरा किया जाता है, तो आपको इस कोर्स से सबसे ज़्यादा फ़ायदा मिलेगा. अपनी जानकारी के हिसाब से, कुछ सेक्शन को सरसरी तौर पर पढ़ा जा सकता है. यह कोर्स उन प्रोग्रामर के लिए है जिन्हें ऑब्जेक्ट ओरिएंटेड लैंग्वेज के बारे में जानकारी है और जो Kotlin सीखना चाहते हैं.

परिचय

इस कोडलैब में, आपको सामान्य क्लास, फ़ंक्शन, और तरीकों के बारे में बताया गया है. साथ ही, यह भी बताया गया है कि ये Kotlin में कैसे काम करते हैं.

इस कोर्स में, एक सैंपल ऐप्लिकेशन बनाने के बजाय, अलग-अलग विषयों पर जानकारी दी गई है. हालांकि, ये विषय एक-दूसरे से जुड़े हुए हैं, लेकिन इन्हें इस तरह से डिज़ाइन किया गया है कि आप अपनी ज़रूरत के हिसाब से किसी भी विषय को पढ़ सकें. इन सभी उदाहरणों को एक साथ दिखाने के लिए, इनमें ऐक्वेरियम की थीम का इस्तेमाल किया गया है. अगर आपको एक्वेरियम की पूरी कहानी देखनी है, तो Udacity पर प्रोग्रामर के लिए Kotlin बूटकैंप कोर्स देखें.

आपको पहले से क्या पता होना चाहिए

  • Kotlin के फ़ंक्शन, क्लास, और तरीकों का सिंटैक्स
  • IntelliJ IDEA में नई क्लास बनाने और प्रोग्राम चलाने का तरीका

आपको क्या सीखने को मिलेगा

  • जेनेरिक क्लास, मेथड, और फ़ंक्शन का इस्तेमाल कैसे करें

आपको क्या करना होगा

  • जेनेरिक क्लास बनाना और उसमें शर्तें जोड़ना
  • in और out टाइप बनाना
  • जेनेरिक फ़ंक्शन, मेथड, और एक्सटेंशन फ़ंक्शन बनाना

जेनेरिक के बारे में जानकारी

Kotlin में, कई प्रोग्रामिंग लैंग्वेज की तरह जेनेरिक टाइप होते हैं. सामान्य टाइप की मदद से, किसी क्लास को सामान्य बनाया जा सकता है. इससे क्लास को ज़्यादा फ़्लेक्सिबल बनाया जा सकता है.

मान लें कि आपको एक MyList क्लास लागू करनी है, जिसमें आइटम की सूची होती है. जेनेरिक के बिना, आपको हर टाइप के लिए MyList का नया वर्शन लागू करना होगा: एक Double के लिए, एक String के लिए, और एक Fish के लिए. जेनेरिक की मदद से, सूची को जेनेरिक बनाया जा सकता है, ताकि इसमें किसी भी तरह की ऑब्जेक्ट को शामिल किया जा सके. यह टाइप को वाइल्डकार्ड बनाने जैसा है, जो कई टाइप के लिए सही होगा.

किसी सामान्य टाइप को तय करने के लिए, क्लास के नाम के बाद ऐंगल ब्रैकेट <T> में T लिखें. (किसी अन्य अक्षर या लंबे नाम का इस्तेमाल किया जा सकता है, लेकिन सामान्य टाइप के लिए T का इस्तेमाल किया जाता है.)

class MyList<T> {
    fun get(pos: Int): T {
        TODO("implement")
    }
    fun addItem(item: T) {}
}

T को सामान्य टाइप की तरह रेफ़र किया जा सकता है. get() का रिटर्न टाइप T है. साथ ही, addItem() का पैरामीटर T टाइप का है. बेशक, सामान्य सूचियां बहुत काम की होती हैं. इसलिए, List क्लास Kotlin में पहले से मौजूद होती है.

पहला चरण: टाइप हैरारकी बनाना

इस चरण में, आपको कुछ क्लास बनानी होंगी, ताकि उन्हें अगले चरण में इस्तेमाल किया जा सके. सबक्लासिंग के बारे में पहले के कोडलैब में बताया गया था. हालांकि, यहां इसकी खास जानकारी दी गई है.

  1. उदाहरण को साफ़-सुथरा रखने के लिए, src के तहत एक नया पैकेज बनाएं और उसे generics नाम दें.
  2. generics पैकेज में, एक नई Aquarium.kt फ़ाइल बनाएं. इससे आपको एक ही नाम का इस्तेमाल करके, बिना किसी समस्या के चीज़ों को फिर से तय करने की सुविधा मिलती है. इसलिए, इस कोडलैब के लिए बाकी कोड इस फ़ाइल में जाता है.
  3. पानी की सप्लाई के टाइप का पदानुक्रम बनाएं. सबसे पहले WaterSupply को open क्लास बनाएं, ताकि इसे सब-क्लास किया जा सके.
  4. बूलियन var पैरामीटर needsProcessing जोड़ें. इससे, गेटर और सेटर के साथ-साथ एक ऐसी प्रॉपर्टी अपने-आप बन जाती है जिसे बदला जा सकता है.
  5. WaterSupply को बढ़ाने वाली TapWater सबक्लास बनाएं और needsProcessing के लिए true पास करें, क्योंकि नल के पानी में ऐसे ऐक्टिव इंग्रीडिएंट होते हैं जो मछली के लिए नुकसानदेह होते हैं.
  6. TapWater में, addChemicalCleaners() नाम का एक फ़ंक्शन तय करें. यह फ़ंक्शन, पानी साफ़ करने के बाद needsProcessing को false पर सेट करता है. needsProcessing प्रॉपर्टी को TapWater से सेट किया जा सकता है, क्योंकि यह डिफ़ॉल्ट रूप से public होती है और सबक्लास के लिए उपलब्ध होती है. यहां पूरा कोड दिया गया है.
package generics

open class WaterSupply(var needsProcessing: Boolean)

class TapWater : WaterSupply(true) {
   fun addChemicalCleaners() {
       needsProcessing = false
   }
}
  1. WaterSupply की दो और सबक्लास बनाएं, जिन्हें FishStoreWater और LakeWater कहा जाता है. FishStoreWater को प्रोसेस करने की ज़रूरत नहीं है, लेकिन LakeWater को filter() तरीके से फ़िल्टर करना ज़रूरी है. फ़िल्टर करने के बाद, इसे फिर से प्रोसेस करने की ज़रूरत नहीं होती. इसलिए, filter() में needsProcessing = false सेट करें.
class FishStoreWater : WaterSupply(false)

class LakeWater : WaterSupply(true) {
   fun filter() {
       needsProcessing = false
   }
}

अगर आपको ज़्यादा जानकारी चाहिए, तो Kotlin में इनहेरिटेंस के बारे में पहले से दिया गया लेख पढ़ें.

दूसरा चरण: जेनेरिक क्लास बनाना

इस चरण में, अलग-अलग तरह की पानी की सप्लाई के लिए, Aquarium क्लास में बदलाव किया जाता है.

  1. Aquarium.kt में, Aquarium क्लास को परिभाषित करें. इसके लिए, क्लास के नाम के बाद ब्रैकेट में <T> लिखें.
  2. Aquarium में, T टाइप की एक ऐसी प्रॉपर्टी waterSupply जोड़ें जिसे बदला नहीं जा सकता.
class Aquarium<T>(val waterSupply: T)
  1. genericsExample() नाम का एक फ़ंक्शन लिखें. यह किसी क्लास का हिस्सा नहीं है. इसलिए, इसे फ़ाइल के टॉप लेवल पर रखा जा सकता है. जैसे, main() फ़ंक्शन या क्लास की परिभाषाएं. फ़ंक्शन में, Aquarium बनाएं और उसे WaterSupply पास करें. waterSupply पैरामीटर सामान्य है. इसलिए, आपको ऐंगल ब्रैकेट <> में टाइप तय करना होगा.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
}
  1. genericsExample() में आपका कोड, ऐक्वेरियम के waterSupply को ऐक्सेस कर सकता है. यह TapWater टाइप का है. इसलिए, बिना किसी टाइप कास्ट के addChemicalCleaners() को कॉल किया जा सकता है.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. Aquarium ऑब्जेक्ट बनाते समय, ऐंगल ब्रैकेट और उनके बीच मौजूद कॉन्टेंट को हटाया जा सकता है. ऐसा इसलिए, क्योंकि Kotlin में टाइप का अनुमान होता है. इसलिए, इंस्टेंस बनाते समय TapWater को दो बार इस्तेमाल करने की कोई वजह नहीं है. Aquarium के आर्ग्युमेंट से टाइप का अनुमान लगाया जा सकता है; यह अब भी TapWater टाइप का Aquarium बनाएगा.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. यह देखने के लिए कि क्या हो रहा है, addChemicalCleaners() को कॉल करने से पहले और बाद में needsProcessing प्रिंट करें. यहां पूरा किया गया फ़ंक्शन दिया गया है.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
    aquarium.waterSupply.addChemicalCleaners()
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
}
  1. genericsExample() को कॉल करने के लिए, main() फ़ंक्शन जोड़ें. इसके बाद, अपना प्रोग्राम चलाएं और नतीजे देखें.
fun main() {
    genericsExample()
}
⇒ water needs processing: true
water needs processing: false

तीसरा चरण: इसे ज़्यादा खास बनाना

सामान्य का मतलब है कि इसमें लगभग किसी भी तरह की जानकारी शामिल की जा सकती है. हालांकि, कभी-कभी यह समस्या पैदा कर सकती है. इस चरण में, Aquarium क्लास को ज़्यादा खास बनाया जाता है, ताकि यह पता चल सके कि इसमें क्या-क्या शामिल किया जा सकता है.

  1. genericsExample() में, Aquarium बनाएं. इसके लिए, waterSupply के लिए स्ट्रिंग पास करें. इसके बाद, ऐक्वेरियम की waterSupply प्रॉपर्टी प्रिंट करें.
fun genericsExample() {
    val aquarium2 = Aquarium("string")
    println(aquarium2.waterSupply)
}
  1. प्रोग्राम चलाएं और नतीजे देखें.
⇒ string

नतीजा वह स्ट्रिंग है जिसे आपने पास किया है, क्योंकि Aquarium किसी भी तरह की सीमाएं नहीं लगाता है. इसमें String भी शामिल है.T.

  1. genericsExample() में, waterSupply के लिए null पास करके, एक और Aquarium बनाएं. अगर waterSupply शून्य है, तो "waterSupply is null" प्रिंट करें.
fun genericsExample() {
    val aquarium3 = Aquarium(null)
    if (aquarium3.waterSupply == null) {
        println("waterSupply is null")
    }
}
  1. प्रोग्राम चलाएं और नतीजे देखें.
⇒ waterSupply is null

Aquarium बनाते समय, null को क्यों पास किया जा सकता है? ऐसा इसलिए हो सकता है, क्योंकि डिफ़ॉल्ट रूप से, T का मतलब है कि यह Any? टाइप को शून्य किया जा सकता है. यह टाइप, टाइप हैरारकी में सबसे ऊपर होता है. यहां दिया गया जवाब, उस जवाब से मिलता-जुलता है जो आपने पहले टाइप किया था.

class Aquarium<T: Any?>(val waterSupply: T)
  1. null को पास करने की अनुमति न देने के लिए, Any के बाद मौजूद ? को हटाकर, T को साफ़ तौर पर Any टाइप का बनाएं.
class Aquarium<T: Any>(val waterSupply: T)

इस संदर्भ में, Any को सामान्य कंस्ट्रेंट कहा जाता है. इसका मतलब है कि T के लिए किसी भी टाइप को पास किया जा सकता है, बशर्ते कि वह null न हो.

  1. आपको यह पक्का करना है कि T के लिए सिर्फ़ WaterSupply (या इसकी किसी सबक्लास) को पास किया जा सके. ज़्यादा सटीक सामान्य शर्त तय करने के लिए, Any को WaterSupply से बदलें.
class Aquarium<T: WaterSupply>(val waterSupply: T)

चौथा चरण: ज़्यादा जांच करना

इस चरण में, आपको check() फ़ंक्शन के बारे में बताया गया है. इससे यह पक्का करने में मदद मिलती है कि आपका कोड आपकी उम्मीद के मुताबिक काम कर रहा है. check(), Kotlin में स्टैंडर्ड लाइब्रेरी फ़ंक्शन है. यह एक दावा है और अगर इसके तर्क का आकलन false के तौर पर किया जाता है, तो यह IllegalStateException दिखाएगा.

  1. पानी जोड़ने के लिए, Aquarium क्लास में addWater() तरीका जोड़ें. साथ ही, एक check() जोड़ें, ताकि पानी को पहले प्रोसेस न करना पड़े.
class Aquarium<T: WaterSupply>(val waterSupply: T) {
    fun addWater() {
        check(!waterSupply.needsProcessing) { "water supply needs processing first" }
        println("adding water from $waterSupply")
    }    
}

इस मामले में, अगर needsProcessing सही है, तो check() एक अपवाद देगा.

  1. genericsExample() में, LakeWater की मदद से Aquarium बनाने के लिए कोड जोड़ें. इसके बाद, उसमें थोड़ा पानी डालें.
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.addWater()
}
  1. अपने प्रोग्राम को चलाएं. आपको एक अपवाद मिलेगा, क्योंकि पानी को पहले फ़िल्टर करना होगा.
⇒ Exception in thread "main" java.lang.IllegalStateException: water supply needs processing first
        at Aquarium.generics.Aquarium.addWater(Aquarium.kt:21)
  1. Aquarium में पानी डालने से पहले, उसे फ़िल्टर करें. अब प्रोग्राम चलाने पर, कोई अपवाद नहीं दिखता.
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.waterSupply.filter()
    aquarium4.addWater()
}
⇒ adding water from generics.LakeWater@880ec60

ऊपर दी गई जानकारी में, जेनेरिक के बारे में बुनियादी बातें बताई गई हैं. यहां दिए गए टास्क में ज़्यादा जानकारी शामिल है. हालांकि, अहम कॉन्सेप्ट यह है कि जेनेरिक कंस्ट्रेंट के साथ जेनेरिक क्लास को कैसे डिक्लेयर और इस्तेमाल किया जाए.

इस टास्क में, आपको जेनेरिक के साथ इन और आउट टाइप के बारे में जानकारी मिलेगी. in एक ऐसा टाइप है जिसे सिर्फ़ किसी क्लास में पास किया जा सकता है, लेकिन वापस नहीं किया जा सकता. out टाइप, एक ऐसा टाइप होता है जिसे सिर्फ़ किसी क्लास से वापस लाया जा सकता है.

Aquarium क्लास को देखें. आपको पता चलेगा कि प्रॉपर्टी waterSupply को पाने पर ही, सामान्य टाइप वापस मिलता है. ऐसे कोई भी तरीके नहीं हैं जो T टाइप की वैल्यू को पैरामीटर के तौर पर लेते हों. हालांकि, इसे कंस्ट्रक्टर में तय किया जा सकता है. Kotlin में, ठीक इसी मामले के लिए out टाइप तय किए जा सकते हैं. साथ ही, यह इस बारे में अतिरिक्त जानकारी का अनुमान लगा सकता है कि टाइप का इस्तेमाल कहां सुरक्षित है. इसी तरह, सामान्य टाइप के लिए in टाइप तय किए जा सकते हैं. ये टाइप सिर्फ़ तरीकों में पास किए जाते हैं, न कि वापस किए जाते हैं. इससे Kotlin को कोड की सुरक्षा के लिए अतिरिक्त जांच करने की अनुमति मिलती है.

in और out टाइप, Kotlin के टाइप सिस्टम के लिए निर्देश हैं. टाइप सिस्टम के बारे में पूरी जानकारी देना, इस बूटकैंप के दायरे से बाहर है. हालांकि, कंपाइलर उन टाइप को फ़्लैग करेगा जिन्हें in और out के तौर पर सही तरीके से मार्क नहीं किया गया है. इसलिए, आपको इनके बारे में पता होना चाहिए.

पहला चरण: आउट टाइप तय करना

  1. Aquarium क्लास में, T: WaterSupply को out टाइप में बदलें.
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    ...
}
  1. उसी फ़ाइल में, क्लास के बाहर, addItemTo() फ़ंक्शन का एलान करें. यह WaterSupply का Aquarium लेता है.
fun addItemTo(aquarium: Aquarium<WaterSupply>) = println("item added")
  1. genericsExample() से addItemTo() को कॉल करें और अपना प्रोग्राम चलाएं.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    addItemTo(aquarium)
}
⇒ item added

Kotlin यह पक्का कर सकता है कि addItemTo(), सामान्य WaterSupply के साथ टाइप से जुड़ी कोई भी असुरक्षित कार्रवाई न करे, क्योंकि इसे out टाइप के तौर पर एलान किया गया है.

  1. out कीवर्ड हटाने पर, कंपाइलर addItemTo() को कॉल करते समय गड़बड़ी का मैसेज दिखाएगा. इसकी वजह यह है कि Kotlin यह पक्का नहीं कर सकता कि टाइप के साथ कोई असुरक्षित कार्रवाई नहीं की जा रही है.

दूसरा चरण: 'इन' टाइप तय करना

in टाइप, out टाइप जैसा ही होता है. हालांकि, यह सिर्फ़ ऐसे सामान्य टाइप के लिए होता है जिन्हें सिर्फ़ फ़ंक्शन में पास किया जाता है, न कि उनसे वापस लिया जाता है. in टाइप को वापस लाने की कोशिश करने पर, आपको कंपाइलर से जुड़ी गड़बड़ी का मैसेज मिलेगा. इस उदाहरण में, इंटरफ़ेस के हिस्से के तौर पर in टाइप को तय किया जाएगा.

  1. Aquarium.kt में, एक इंटरफ़ेस Cleaner तय करें. यह एक सामान्य T लेता है, जो WaterSupply तक सीमित है. इसका इस्तेमाल सिर्फ़ clean() के आर्ग्युमेंट के तौर पर किया जाता है. इसलिए, इसे in पैरामीटर बनाया जा सकता है.
interface Cleaner<in T: WaterSupply> {
    fun clean(waterSupply: T)
}
  1. Cleaner इंटरफ़ेस का इस्तेमाल करने के लिए, एक क्लास TapWaterCleaner बनाएं. यह क्लास, केमिकल मिलाकर TapWater को साफ़ करने के लिए Cleaner को लागू करती है.
class TapWaterCleaner : Cleaner<TapWater> {
    override fun clean(waterSupply: TapWater) =   waterSupply.addChemicalCleaners()
}
  1. Aquarium क्लास में, addWater() को अपडेट करें, ताकि T टाइप का Cleaner लिया जा सके. इसके बाद, पानी को साफ़ करके डालें.
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    fun addWater(cleaner: Cleaner<T>) {
        if (waterSupply.needsProcessing) {
            cleaner.clean(waterSupply)
        }
        println("water added")
    }
}
  1. genericsExample() के उदाहरण कोड को अपडेट करके, TapWaterCleaner, TapWater के साथ Aquarium बनाएं. इसके बाद, क्लीनर का इस्तेमाल करके कुछ पानी डालें. यह क्लीनर का इस्तेमाल ज़रूरत के हिसाब से करेगा.
fun genericsExample() {
    val cleaner = TapWaterCleaner()
    val aquarium = Aquarium(TapWater())
    aquarium.addWater(cleaner)
}

Kotlin, in और out टाइप की जानकारी का इस्तेमाल करेगा. इससे यह पक्का किया जा सकेगा कि आपका कोड, जेनेरिक का सुरक्षित तरीके से इस्तेमाल करता है. Out और in को याद रखना आसान है: out टाइप को रिटर्न वैल्यू के तौर पर बाहर की ओर पास किया जा सकता है, जबकि in टाइप को तर्क के तौर पर अंदर की ओर पास किया जा सकता है.

अगर आपको इनपुट टाइप और आउटपुट टाइप से जुड़ी समस्याओं के बारे में ज़्यादा जानना है, तो दस्तावेज़ में इनके बारे में पूरी जानकारी दी गई है.

इस टास्क में, आपको सामान्य फ़ंक्शन और उनके इस्तेमाल के बारे में जानकारी मिलेगी. आम तौर पर, जब फ़ंक्शन किसी ऐसी क्लास का आर्ग्युमेंट लेता है जिसका सामान्य टाइप होता है, तो सामान्य फ़ंक्शन बनाना एक अच्छा विकल्प होता है.

पहला चरण: एक सामान्य फ़ंक्शन बनाना

  1. generics/Aquarium.kt में, एक ऐसा फ़ंक्शन isWaterClean() बनाएं जो Aquarium लेता हो. आपको पैरामीटर का सामान्य टाइप बताना होगा. इसके लिए, WaterSupply का इस्तेमाल किया जा सकता है.
fun isWaterClean(aquarium: Aquarium<WaterSupply>) {
   println("aquarium water is clean: ${aquarium.waterSupply.needsProcessing}")
}

हालांकि, इसका मतलब यह है कि इस फ़ंक्शन को कॉल करने के लिए, Aquarium में out टाइप का पैरामीटर होना चाहिए. कभी-कभी out या in का इस्तेमाल करना मुश्किल हो जाता है, क्योंकि आपको इनपुट और आउटपुट, दोनों के लिए एक ही टाइप का इस्तेमाल करना होता है. फ़ंक्शन को सामान्य बनाकर, out की ज़रूरत को हटाया जा सकता है.

  1. फ़ंक्शन को सामान्य बनाने के लिए, कीवर्ड fun के बाद ऐंगल ब्रैकेट में सामान्य टाइप T और कोई भी शर्त डालें. इस मामले में, WaterSupply. Aquarium को WaterSupply के बजाय T से सीमित करें.
fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) {
   println("aquarium water is clean: ${!aquarium.waterSupply.needsProcessing}")
}

T, isWaterClean() का एक टाइप पैरामीटर है. इसका इस्तेमाल, ऐक्वेरियम के सामान्य टाइप के बारे में बताने के लिए किया जाता है. यह पैटर्न बहुत सामान्य है. इसलिए, इस पर एक बार काम कर लेना अच्छा होता है.

  1. फ़ंक्शन के नाम के ठीक बाद और ब्रैकेट से पहले, ऐंगल ब्रैकेट में टाइप तय करके isWaterClean() फ़ंक्शन को कॉल करें.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean<TapWater>(aquarium)
}
  1. आर्ग्युमेंट aquarium से टाइप इन्फ़रेंस की वजह से, टाइप की ज़रूरत नहीं है. इसलिए, इसे हटा दें. प्रोग्राम चलाएं और आउटपुट देखें.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean(aquarium)
}
⇒ aquarium water is clean: false

दूसरा चरण: रीफ़ाइड टाइप के साथ एक सामान्य तरीका बनाना

तरीकों के लिए भी सामान्य फ़ंक्शन का इस्तेमाल किया जा सकता है. भले ही, क्लास में अपना सामान्य टाइप हो. इस चरण में, Aquarium में एक सामान्य तरीका जोड़ा जाता है. इससे यह पता चलता है कि इसमें WaterSupply टाइप है या नहीं.

  1. Aquarium क्लास में, hasWaterSupplyOfType() नाम का एक ऐसा मैथड डिक्लेयर करें जो WaterSupply से सीमित किए गए सामान्य पैरामीटर R (T का इस्तेमाल पहले ही किया जा चुका है) को लेता है. साथ ही, अगर waterSupply, R टाइप का है, तो true को रिटर्न करता है. यह उस फ़ंक्शन की तरह है जिसे आपने पहले तय किया था. हालांकि, यह Aquarium क्लास के अंदर है.
fun <R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R
  1. ध्यान दें कि आखिरी R को लाल रंग से अंडरलाइन किया गया है. गड़बड़ी के बारे में जानने के लिए, उस पर पॉइंटर घुमाएं.
  2. is की जांच करने के लिए, आपको Kotlin को यह बताना होगा कि टाइप reified या असली है और इसका इस्तेमाल फ़ंक्शन में किया जा सकता है. इसके लिए, fun कीवर्ड से पहले inline और सामान्य टाइप R से पहले reified लगाएं.
inline fun <reified R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R

किसी टाइप को रीफ़ाई करने के बाद, उसे सामान्य टाइप की तरह इस्तेमाल किया जा सकता है. ऐसा इसलिए, क्योंकि इनलाइन करने के बाद यह एक असली टाइप बन जाता है. इसका मतलब है कि टाइप का इस्तेमाल करके, is जांच की जा सकती है.

अगर यहां reified का इस्तेमाल नहीं किया जाता है, तो टाइप इतना "असली" नहीं होगा कि Kotlin, is की जांच की अनुमति दे सके. ऐसा इसलिए होता है, क्योंकि नॉन-रीफ़ाइड टाइप सिर्फ़ कंपाइल टाइम पर उपलब्ध होते हैं. साथ ही, आपके प्रोग्राम के रनटाइम पर इनका इस्तेमाल नहीं किया जा सकता. इस बारे में, अगले सेक्शन में ज़्यादा जानकारी दी गई है.

  1. टाइप के तौर पर TapWater पास करें. सामान्य फ़ंक्शन को कॉल करने की तरह ही, सामान्य तरीकों को कॉल करें. इसके लिए, फ़ंक्शन के नाम के बाद टाइप के साथ ऐंगल ब्रैकेट का इस्तेमाल करें. प्रोग्राम चलाएं और नतीजे देखें.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())   // true
}
⇒ true

तीसरा चरण: एक्सटेंशन फ़ंक्शन बनाना

रेगुलर फ़ंक्शन और एक्सटेंशन फ़ंक्शन के लिए भी, रीफ़ाइड टाइप का इस्तेमाल किया जा सकता है.

  1. Aquarium क्लास के बाहर, WaterSupply पर isOfType() नाम का एक एक्सटेंशन फ़ंक्शन तय करें. यह फ़ंक्शन, पास किए गए WaterSupply की जांच करता है कि वह किसी खास टाइप का है या नहीं. उदाहरण के लिए, TapWater.
inline fun <reified T: WaterSupply> WaterSupply.isOfType() = this is T
  1. एक्सटेंशन फ़ंक्शन को किसी तरीके की तरह कॉल करें.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.waterSupply.isOfType<TapWater>())  
}
⇒ true

इन एक्सटेंशन फ़ंक्शन के साथ, इससे कोई फ़र्क़ नहीं पड़ता कि Aquarium किस तरह का है (Aquarium या TowerTank या कोई अन्य सबक्लास), जब तक कि वह Aquarium हो. स्टार-प्रोजेक्शन सिंटैक्स का इस्तेमाल करके, अलग-अलग तरह के मैच तय किए जा सकते हैं. स्टार-प्रोजेक्शन का इस्तेमाल करने पर, Kotlin यह भी पक्का करेगा कि आपने कोई असुरक्षित काम न किया हो.

  1. स्टार-प्रोजेक्शन का इस्तेमाल करने के लिए, <*> के बाद Aquarium लिखें. hasWaterSupplyOfType() को एक्सटेंशन फ़ंक्शन के तौर पर मूव करें, क्योंकि यह Aquarium के मुख्य एपीआई का हिस्सा नहीं है.
inline fun <reified R: WaterSupply> Aquarium<*>.hasWaterSupplyOfType() = waterSupply is R
  1. कॉल को hasWaterSupplyOfType() में बदलें और अपना प्रोग्राम चलाएं.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())
}
⇒ true

पिछले उदाहरण में, आपको सामान्य टाइप को reified के तौर पर मार्क करना पड़ा था और फ़ंक्शन को inline बनाना पड़ा था. ऐसा इसलिए, क्योंकि Kotlin को इनके बारे में रनटाइम पर पता होना चाहिए, न कि सिर्फ़ कंपाइल टाइम पर.

सभी सामान्य टाइप का इस्तेमाल, Kotlin सिर्फ़ कंपाइल टाइम पर करता है. इससे कंपाइलर को यह पक्का करने में मदद मिलती है कि आपने सभी काम सुरक्षित तरीके से किए हैं. रनटाइम के हिसाब से, सभी सामान्य टाइप मिटा दिए जाते हैं. इसलिए, मिटाए गए टाइप की जांच करने के बारे में पहले गड़बड़ी का मैसेज दिखता है.

इससे पता चलता है कि कंपाइलर, रनटाइम तक सामान्य टाइप को सेव किए बिना सही कोड बना सकता है. हालांकि, इसका मतलब यह है कि कभी-कभी आपको कुछ ऐसा करना पड़ता है जिसे कंपाइलर सपोर्ट नहीं कर सकता. जैसे, सामान्य टाइप पर is की जांच करना. इसलिए, Kotlin ने reified या real टाइप जोड़े हैं.

Kotlin के दस्तावेज़ में, रीफ़ाइड टाइप और टाइप इरेज़र के बारे में ज़्यादा जानकारी दी गई है.

इस सबक में, जेनेरिक पर फ़ोकस किया गया है. ये कोड को ज़्यादा फ़्लेक्सिबल बनाने और उसे आसानी से दोबारा इस्तेमाल करने के लिए ज़रूरी होते हैं.

  • कोड को ज़्यादा फ़्लेक्सिबल बनाने के लिए, जेनेरिक क्लास बनाएं.
  • जेनेरिक के साथ इस्तेमाल किए जाने वाले टाइप को सीमित करने के लिए, सामान्य कंस्ट्रेंट जोड़ें.
  • जेनेरिक के साथ in और out टाइप का इस्तेमाल करें, ताकि क्लास में पास किए जा रहे या उनसे वापस किए जा रहे टाइप को सीमित करने के लिए, बेहतर टाइप चेकिंग की जा सके.
  • जेनेरिक टाइप के साथ काम करने के लिए, जेनेरिक फ़ंक्शन और तरीके बनाएं. उदाहरण के लिए:
    fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) { ... }
  • किसी क्लास में नॉन-कोर फ़ंक्शनैलिटी जोड़ने के लिए, सामान्य एक्सटेंशन फ़ंक्शन का इस्तेमाल करें.
  • टाइप इरेज़र की वजह से, कभी-कभी रीफ़ाइड टाइप ज़रूरी होते हैं. रीफ़ाइड टाइप, सामान्य टाइप से अलग होते हैं. ये रनटाइम तक बने रहते हैं.
  • check() फ़ंक्शन का इस्तेमाल करके, यह पुष्टि करें कि आपका कोड उम्मीद के मुताबिक काम कर रहा है. उदाहरण के लिए:
    check(!waterSupply.needsProcessing) { "water supply needs processing first" }

Kotlin का दस्तावेज़

अगर आपको इस कोर्स के किसी विषय के बारे में ज़्यादा जानकारी चाहिए या आपको कोई समस्या आ रही है, तो https://kotlinlang.org पर जाएं.

Kotlin के ट्यूटोरियल

https://try.kotlinlang.org वेबसाइट पर, Kotlin Koans नाम के रिच ट्यूटोरियल, वेब पर आधारित इंटरप्रेटर, और उदाहरणों के साथ रेफ़रंस दस्तावेज़ों का पूरा सेट शामिल है.

Udacity कोर्स

इस विषय पर Udacity का कोर्स देखने के लिए, Kotlin Bootcamp for Programmers पर जाएं.

IntelliJ IDEA

IntelliJ IDEA के लिए दस्तावेज़, JetBrains की वेबसाइट पर उपलब्ध हैं.

इस सेक्शन में, उन छात्र-छात्राओं के लिए होमवर्क असाइनमेंट की सूची दी गई है जो किसी शिक्षक के कोर्स के हिस्से के तौर पर इस कोडलैब पर काम कर रहे हैं. शिक्षक के पास ये विकल्प होते हैं:

  • अगर ज़रूरी हो, तो होमवर्क असाइन करें.
  • छात्र-छात्राओं को बताएं कि होमवर्क असाइनमेंट कैसे सबमिट किए जाते हैं.
  • होमवर्क असाइनमेंट को ग्रेड दें.

शिक्षक इन सुझावों का इस्तेमाल अपनी ज़रूरत के हिसाब से कर सकते हैं. साथ ही, वे चाहें, तो कोई दूसरा होमवर्क भी दे सकते हैं.

अगर आपको यह कोडलैब खुद से पूरा करना है, तो अपनी जानकारी की जांच करने के लिए, इन होमवर्क असाइनमेंट का इस्तेमाल करें.

इन सवालों के जवाब दें

पहला सवाल

इनमें से सामान्य टाइप का नाम रखने का तरीका कौनसा है?

<Gen>

<Generic>

<T>

<X>

दूसरा सवाल

किसी सामान्य टाइप के लिए अनुमति वाले टाइप पर लगाई गई पाबंदी को यह कहा जाता है:

▢ सामान्य पाबंदी

▢ सामान्य कंस्ट्रेंट

▢ क्वेरी से अलग जानकारी का शामिल होना (disambiguation)

▢ सामान्य टाइप की सीमा

तीसरा सवाल

रीफ़ाइड का मतलब है:

▢ किसी ऑब्जेक्ट के असल में लागू होने का असर कैलकुलेट किया गया है.

▢ क्लास के लिए, प्रतिबंधित एंट्री इंडेक्स सेट किया गया है.

▢ सामान्य टाइप पैरामीटर को असली टाइप में बदल दिया गया है.

▢ रिमोट गड़बड़ी का इंडिकेटर ट्रिगर हो गया है.

अगले लेसन पर जाएं: 6. फ़ंक्शनल मैनिपुलेशन

कोर्स की खास जानकारी और अन्य कोडलैब के लिंक देखने के लिए, "प्रोग्रामर के लिए Kotlin बूटकैंप: कोर्स में आपका स्वागत है." लेख पढ़ें