(अब काम नहीं करता) Kotlin में बदलना

1. आपका स्वागत है!

इस कोडलैब में, आपको अपने कोड को Java से Kotlin में बदलने का तरीका पता चलेगा. आपको यह भी पता चलेगा कि Kotlin लैंग्वेज के कन्वेंशंस क्या हैं और यह कैसे पक्का किया जा सकता है कि आपका लिखा गया कोड इनका पालन करता हो.

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

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

आपको Java को Kotlin में बदलने का तरीका पता चलेगा. ऐसा करने पर, आपको Kotlin भाषा की ये सुविधाएं और कॉन्सेप्ट के बारे में पता चलेगा:

  • शून्य वैल्यू की अनुमति है या नहीं, यह तय करना
  • सिंगलटन लागू करना
  • डेटा क्लास
  • स्ट्रिंग मैनेज करना
  • एल्विस ऑपरेटर
  • स्ट्रक्चर हटाना
  • प्रॉपर्टी और बैकिंग प्रॉपर्टी
  • डिफ़ॉल्ट आर्ग्युमेंट और नाम वाले पैरामीटर
  • कलेक्शन के साथ काम करना
  • एक्सटेंशन फ़ंक्शन
  • टॉप-लेवल फ़ंक्शन और पैरामीटर
  • let, apply, with, और run कीवर्ड

अनुमान

आपके पास Java के बारे में पहले से जानकारी होनी चाहिए.

आपको किन चीज़ों की ज़रूरत होगी

2. सेट अप करना

नया प्रोजेक्ट बनाना

अगर IntelliJ IDEA का इस्तेमाल किया जा रहा है, तो Kotlin/JVM के साथ एक नया Java प्रोजेक्ट बनाएं.

अगर Android Studio का इस्तेमाल किया जा रहा है, तो कोई गतिविधि नहीं टेंप्लेट का इस्तेमाल करके नया प्रोजेक्ट बनाएं. प्रोजेक्ट की भाषा के तौर पर Kotlin चुनें. SDK टूल के कम से कम वर्शन की वैल्यू कुछ भी हो सकती है. इससे नतीजे पर कोई असर नहीं पड़ेगा.

कोड

हम एक User मॉडल ऑब्जेक्ट और एक Repository सिंगलटन क्लास बनाएंगे, जो User ऑब्जेक्ट के साथ काम करती है. साथ ही, उपयोगकर्ताओं की सूचियां और फ़ॉर्मैट किए गए उपयोगकर्ता नाम दिखाती है.

app/java/<yourpackagename> में जाकर, User.java नाम से नई फ़ाइल बनाएं और नीचे दिया गया कोड चिपकाएं:

public class User {

    @Nullable
    private String firstName;
    @Nullable
    private String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

आपको पता चलेगा कि आपका आईडीई आपको बता रहा है कि @Nullable की परिभाषा नहीं दी गई है. इसलिए, अगर Android Studio का इस्तेमाल किया जा रहा है, तो androidx.annotation.Nullable इंपोर्ट करें. अगर IntelliJ का इस्तेमाल किया जा रहा है, तो org.jetbrains.annotations.Nullable इंपोर्ट करें.

Repository.java नाम से एक नई फ़ाइल बनाएं और नीचे दिया गया कोड चिपकाएं:

import java.util.ArrayList;
import java.util.List;

public class Repository {

    private static Repository INSTANCE = null;

    private List<User> users = null;

    public static Repository getInstance() {
        if (INSTANCE == null) {
            synchronized (Repository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Repository();
                }
            }
        }
        return INSTANCE;
    }

    // keeping the constructor private to enforce the usage of getInstance
    private Repository() {

        User user1 = new User("Jane", "");
        User user2 = new User("John", null);
        User user3 = new User("Anne", "Doe");

        users = new ArrayList();
        users.add(user1);
        users.add(user2);
        users.add(user3);
    }

    public List<User> getUsers() {
        return users;
    }

    public List<String> getFormattedUserNames() {
        List<String> userNames = new ArrayList<>(users.size());
        for (User user : users) {
            String name;

            if (user.getLastName() != null) {
                if (user.getFirstName() != null) {
                    name = user.getFirstName() + " " + user.getLastName();
                } else {
                    name = user.getLastName();
                }
            } else if (user.getFirstName() != null) {
                name = user.getFirstName();
            } else {
                name = "Unknown";
            }
            userNames.add(name);
        }
        return userNames;
    }
}

3. वैल्यू न होने की स्थिति, val, var, और डेटा क्लास का एलान करना

हमारा IDE, Java कोड को Kotlin कोड में अपने-आप बदलने का काम बहुत अच्छी तरह से कर सकता है. हालांकि, कभी-कभी उसे थोड़ी मदद की ज़रूरत पड़ती है. चलिए, अपने आईडीई को कन्वर्ज़न पर शुरुआती पास करने दें. इसके बाद, हम उस कोड की जांच करेंगे जिसे इस तरह बदला गया है. इससे हमें यह समझने में मदद मिलेगी कि इसे इस तरह कैसे और क्यों बदला गया है.

User.java फ़ाइल पर जाएं और उसे Kotlin में बदलें: मेन्यू बार -> कोड -> Java फ़ाइल को Kotlin फ़ाइल में बदलें.

अगर कन्वर्ज़न के बाद आपका आईडीई, सुधार करने के लिए कहता है, तो हां दबाएं.

e6f96eace5dabe5f.png

आपको यह Kotlin कोड दिखेगा:

class User(var firstName: String?, var lastName: String?)

ध्यान दें कि User.java का नाम बदलकर User.kt कर दिया गया है. Kotlin फ़ाइलों का एक्सटेंशन .kt होता है.

हमारी Java User क्लास में दो प्रॉपर्टी थीं: firstName और lastName. हर एलिमेंट में, गेट्टर और सेटर मेथड होते थे, जिससे उसकी वैल्यू में बदलाव किया जा सकता था. बदलाव किए जा सकने वाले वैरिएबल के लिए Kotlin का कीवर्ड var है. इसलिए, कन्वर्टर इनमें से हर प्रॉपर्टी के लिए var का इस्तेमाल करता है. अगर हमारी Java प्रॉपर्टी में सिर्फ़ गेट्टर थे, तो वे रीड-ओनली होतीं और उन्हें val वैरिएबल के तौर पर घोषित किया जाता. val, Java में final कीवर्ड जैसा ही है.

Kotlin और Java के बीच का एक मुख्य अंतर यह है कि Kotlin साफ़ तौर पर बताता है कि कोई वैरिएबल, शून्य वैल्यू स्वीकार कर सकता है या नहीं. यह टाइप डिक्लेरेशन में ? जोड़कर ऐसा करता है.

हमने firstName और lastName को 'शून्य वैल्यू हो सकती है' के तौर पर मार्क किया था. इसलिए, अपने-आप बदलने वाले टूल ने प्रॉपर्टी को String? के साथ 'शून्य वैल्यू हो सकती है' के तौर पर अपने-आप मार्क कर दिया. अगर आपने अपने Java सदस्यों को org.jetbrains.annotations.NotNull या androidx.annotation.NonNull का इस्तेमाल करके, नॉन-नल के तौर पर एनोटेट किया है, तो कन्वर्टर इसे पहचान लेगा और Kotlin में भी फ़ील्ड को नॉन-नल बना देगा.

बुनियादी कन्वर्ज़न पहले ही हो चुका है. हालांकि, इसे और भी आसानी से लिखा जा सकता है. आइए, देखें कि कैसे.

डेटा क्लास

हमारी User क्लास में सिर्फ़ डेटा सेव होता है. Kotlin में इस भूमिका वाली क्लास के लिए एक कीवर्ड है: data. इस क्लास को data क्लास के तौर पर मार्क करने पर, कंपाइलर हमारे लिए गैटर और सेटर अपने-आप बना देगा. इससे equals(), hashCode(), और toString() फ़ंक्शन भी मिलेंगे.

चलिए, अपनी User क्लास में data कीवर्ड जोड़ते हैं:

data class User(var firstName: String?, var lastName: String?)

Java की तरह, Kotlin में भी एक मुख्य कंस्ट्रक्टर और एक या उससे ज़्यादा सेकंडरी कंस्ट्रक्टर हो सकते हैं. ऊपर दिए गए उदाहरण में, User क्लास का मुख्य कंस्ट्रक्टर है. अगर आपको एक से ज़्यादा कंस्ट्रक्टर वाली Java क्लास को Kotlin में बदलना है, तो कन्वर्टर Kotlin में भी अपने-आप एक से ज़्यादा कंस्ट्रक्टर बना देगा. इन्हें constructor कीवर्ड का इस्तेमाल करके तय किया जाता है.

अगर हमें इस क्लास का इंस्टेंस बनाना है, तो ऐसा इस तरह किया जा सकता है:

val user1 = User("Jane", "Doe")

समानता

Kotlin में, बराबर होने की दो तरह की जांच की जा सकती है:

  • स्ट्रक्चरल इक्विलिटी, == ऑपरेटर का इस्तेमाल करती है. साथ ही, यह तय करने के लिए equals() को कॉल करती है कि दो इंस्टेंस एक जैसे हैं या नहीं.
  • रेफ़रंस के बराबर होने की जांच करने के लिए, === ऑपरेटर का इस्तेमाल किया जाता है. साथ ही, यह भी देखा जाता है कि दो रेफ़रंस एक ही ऑब्जेक्ट पर ले जाते हैं या नहीं.

डेटा क्लास के प्राइमरी कन्स्ट्रक्टर में तय की गई प्रॉपर्टी का इस्तेमाल, स्ट्रक्चर की समानता की जांच करने के लिए किया जाएगा.

val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false

4. डिफ़ॉल्ट आर्ग्युमेंट, नाम वाले आर्ग्युमेंट

Kotlin में, फ़ंक्शन कॉल में आर्ग्युमेंट को डिफ़ॉल्ट वैल्यू असाइन की जा सकती हैं. आर्ग्युमेंट को छोड़ने पर, डिफ़ॉल्ट वैल्यू का इस्तेमाल किया जाता है. Kotlin में, कंस्ट्रक्टर भी फ़ंक्शन होते हैं. इसलिए, हम डिफ़ॉल्ट आर्ग्युमेंट का इस्तेमाल करके यह बता सकते हैं कि lastName की डिफ़ॉल्ट वैल्यू null है. इसके लिए, हम null को lastName पर असाइन करते हैं.

data class User(var firstName: String?, var lastName: String? = null)

// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("Joe", "Doe")

Kotlin में, फ़ंक्शन कॉल किए जाने पर आर्ग्युमेंट को लेबल किया जा सकता है:

val john = User(firstName = "John", lastName = "Doe") 

किसी दूसरे इस्तेमाल के उदाहरण के तौर पर, मान लें कि firstName की डिफ़ॉल्ट वैल्यू null है और lastName की नहीं. इस मामले में, डिफ़ॉल्ट पैरामीटर, बिना डिफ़ॉल्ट वैल्यू वाले पैरामीटर से पहले होगा. इसलिए, आपको नाम वाले आर्ग्युमेंट के साथ फ़ंक्शन को कॉल करना होगा:

data class User(var firstName: String? = null, var lastName: String?)

// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")

डिफ़ॉल्ट वैल्यू, Kotlin कोड में एक अहम और अक्सर इस्तेमाल होने वाला कॉन्सेप्ट है. अपने कोडलैब में, हम User ऑब्जेक्ट के एलान में हमेशा नाम और उपनाम बताना चाहते हैं, ताकि हमें डिफ़ॉल्ट वैल्यू की ज़रूरत न पड़े.

5. ऑब्जेक्ट को शुरू करना, कंपैनियन ऑब्जेक्ट, और सिंगलटन

कोडलैब जारी रखने से पहले, पक्का करें कि आपकी User क्लास, data क्लास हो. अब, Repository क्लास को Kotlin में बदलते हैं. अपने-आप कन्वर्ज़न होने का नतीजा कुछ ऐसा दिखेगा:

import java.util.*

class Repository private constructor() {
    private var users: MutableList<User?>? = null
    fun getUsers(): List<User?>? {
        return users
    }

    val formattedUserNames: List<String?>
        get() {
            val userNames: MutableList<String?> =
                ArrayList(users!!.size)
            for (user in users) {
                var name: String
                name = if (user!!.lastName != null) {
                    if (user!!.firstName != null) {
                        user!!.firstName + " " + user!!.lastName
                    } else {
                        user!!.lastName
                    }
                } else if (user!!.firstName != null) {
                    user!!.firstName
                } else {
                    "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

    companion object {
        private var INSTANCE: Repository? = null
        val instance: Repository?
            get() {
                if (INSTANCE == null) {
                    synchronized(Repository::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE =
                                Repository()
                        }
                    }
                }
                return INSTANCE
            }
    }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

आइए देखें कि ऑटोमैटिक कन्वर्टर ने क्या किया:

  • users की सूची में वैल्यू मौजूद नहीं हो सकती, क्योंकि ऑब्जेक्ट को एलान करने के समय इंस्टैंशिएट नहीं किया गया था
  • Kotlin में getUsers() जैसे फ़ंक्शन का एलान, fun मॉडिफ़ायर के साथ किया जाता है
  • getFormattedUserNames() तरीका अब formattedUserNames नाम की प्रॉपर्टी है
  • उपयोगकर्ताओं की सूची (जो शुरुआत में getFormattedUserNames( का हिस्सा थी) पर किए गए दोहराव का सिंटैक्स, Java के सिंटैक्स से अलग है
  • static फ़ील्ड अब companion object ब्लॉक का हिस्सा है
  • init ब्लॉक जोड़ा गया

आगे बढ़ने से पहले, आइए कोड को थोड़ा सा बेहतर बनाते हैं. कन्स्ट्रक्टर में जाकर, हमें पता चलता है कि कन्वर्टर ने हमारी users सूची को बदलाव की सुविधा वाली सूची बना दिया है, जिसमें शून्य वैल्यू वाले ऑब्जेक्ट शामिल हैं. सूची में शून्य वैल्यू हो सकती है, लेकिन मान लें कि इसमें शून्य उपयोगकर्ता नहीं हो सकते. इसलिए, यह तरीका अपनाएं:

  • users टाइप के एलान में, User? में मौजूद ? को हटाएं
  • getUsers() के टाइप के लिए, User? में ? हटाएं, ताकि यह List<User>? दिखाए

Init ब्लॉक

Kotlin में, मुख्य कंस्ट्रक्टर में कोई कोड नहीं हो सकता. इसलिए, शुरुआती कोड को init ब्लॉक में रखा जाता है. इन दोनों का काम एक जैसा है.

class Repository private constructor() {
    ...
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

init कोड का ज़्यादातर हिस्सा, प्रॉपर्टी को शुरू करने की प्रोसेस को मैनेज करता है. ऐसा प्रॉपर्टी के एलान में भी किया जा सकता है. उदाहरण के लिए, Repository क्लास के Kotlin वर्शन में, हमें पता चलता है कि उपयोगकर्ता प्रॉपर्टी को एलान में शुरू किया गया था.

private var users: MutableList<User>? = null

Kotlin की static प्रॉपर्टी और मेथड

Java में, फ़ील्ड या फ़ंक्शन के लिए static कीवर्ड का इस्तेमाल किया जाता है. इससे पता चलता है कि वे किसी क्लास से जुड़े हैं, न कि क्लास के इंस्टेंस से. इसलिए, हमने अपनी Repository क्लास में INSTANCE स्टैटिक फ़ील्ड बनाया है. Kotlin में, इसके बराबर companion object ब्लॉक होता है. यहां आपको स्टैटिक फ़ील्ड और स्टैटिक फ़ंक्शन भी एलान करने होंगे. कन्वर्टर ने कंपैनियन ऑब्जेक्ट ब्लॉक बनाया और INSTANCE फ़ील्ड को यहां ले आया.

सिंगलटन हैंडल करना

हमें Repository क्लास का सिर्फ़ एक इंस्टेंस चाहिए, इसलिए हमने Java में सिंगलटन पैटर्न का इस्तेमाल किया. Kotlin में, class कीवर्ड को object से बदलकर, कंपाइलर लेवल पर इस पैटर्न को लागू किया जा सकता है.

निजी कंस्ट्रक्टर हटाएं और क्लास की परिभाषा को object Repository से बदलें. साथी ऑब्जेक्ट को भी हटाएं.

object Repository {

    private var users: MutableList<User>? = null
    fun getUsers(): List<User>? {
       return users
    }

    val formattedUserNames: List<String>
        get() {
            val userNames: MutableList<String> =
                ArrayList(users!!.size)
        for (user in users) {
            var name: String
            name = if (user!!.lastName != null) {
                if (user!!.firstName != null) {
                    user!!.firstName + " " + user!!.lastName
                } else {
                    user!!.lastName
                }
            } else if (user!!.firstName != null) {
                user!!.firstName
            } else {
                "Unknown"
            }
            userNames.add(name)
       }
       return userNames
   }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

object क्लास का इस्तेमाल करते समय, हम सीधे ऑब्जेक्ट पर फ़ंक्शन और प्रॉपर्टी को कॉल करते हैं. जैसे:

val formattedUserNames = Repository.formattedUserNames

ध्यान दें कि अगर किसी प्रॉपर्टी में विज़िबिलिटी मॉडिफ़ायर नहीं है, तो वह डिफ़ॉल्ट रूप से सार्वजनिक होती है. जैसे, Repository ऑब्जेक्ट में formattedUserNames प्रॉपर्टी.

6. शून्य वैल्यू की अनुमति है या नहीं, यह तय करना

Repository क्लास को Kotlin में बदलते समय, ऑटोमैटिक कन्वर्टर ने उपयोगकर्ताओं की सूची को शून्य वैल्यू के साथ इस्तेमाल करने लायक बना दिया, क्योंकि इसे एलान करते समय किसी ऑब्जेक्ट के तौर पर शुरू नहीं किया गया था. इसलिए, users ऑब्जेक्ट के सभी इस्तेमाल के लिए, नॉन-शून्य एश्योरेशन ऑपरेटर !! का इस्तेमाल करना ज़रूरी है. (आपको बदले गए कोड में users!! और user!! दिखेंगे.) !! ऑपरेटर किसी भी वैरिएबल को नॉन-नल टाइप में बदल देता है, ताकि आप उस पर प्रॉपर्टी ऐक्सेस कर सकें या फ़ंक्शन कॉल कर सकें. हालांकि, अगर वैरिएबल की वैल्यू शून्य है, तो अपवाद दिखेगा. !! का इस्तेमाल करने पर, रनटाइम के दौरान अपवाद मिलने का खतरा होता है.

इसके बजाय, इनमें से किसी एक तरीके का इस्तेमाल करके, वैल्यू न होने की स्थिति को मैनेज करें:

  • शून्य की जांच करना ( if (users != null) {...} )
  • एल्विस ऑपरेटर ?: का इस्तेमाल करके (इसके बारे में कोडलैब में आगे बताया गया है)
  • Kotlin के कुछ स्टैंडर्ड फ़ंक्शन का इस्तेमाल करना (इस बारे में कोडलैब में आगे बताया गया है)

हमारे मामले में, हम जानते हैं कि उपयोगकर्ताओं की सूची के लिए, वैल्यू न होने की शर्त लागू नहीं होती, क्योंकि इसे ऑब्जेक्ट बनाने के तुरंत बाद (init ब्लॉक में) शुरू किया जाता है. इसलिए, users ऑब्जेक्ट का एलान करते समय, हम उसे सीधे इंस्टैंशिएट कर सकते हैं.

कलेक्शन टाइप के इंस्टेंस बनाते समय, Kotlin कई सहायक फ़ंक्शन उपलब्ध कराता है. इनकी मदद से, आपके कोड को आसानी से पढ़ा जा सकता है और उसमें बदलाव किया जा सकता है. यहां हम users के लिए MutableList का इस्तेमाल कर रहे हैं:

private var users: MutableList<User>? = null

आसानी से समझने के लिए, हम mutableListOf() फ़ंक्शन का इस्तेमाल करके, सूची के एलिमेंट का टाइप दे सकते हैं. mutableListOf<User>(), एक खाली सूची बनाता है जिसमें User ऑब्जेक्ट हो सकते हैं. अब कंपाइलर, वैरिएबल के डेटा टाइप का अनुमान लगा सकता है. इसलिए, users प्रॉपर्टी के टाइप के एलान को हटा दें.

private val users = mutableListOf<User>()

हमने var को val में भी बदल दिया है, क्योंकि उपयोगकर्ताओं के पास उपयोगकर्ताओं की सूची का सिर्फ़ पढ़ने का ऐक्सेस होगा. ध्यान दें कि रेफ़रंस सिर्फ़ पढ़ने के लिए है. इसलिए, यह कभी भी किसी नई सूची पर नहीं ले जा सकता. हालांकि, सूची में अब भी बदलाव किया जा सकता है. जैसे, एलिमेंट जोड़े या हटाए जा सकते हैं.

users वैरिएबल पहले से ही शुरू हो चुका है, इसलिए init ब्लॉक से इस शुरू करने की प्रोसेस को हटाएं:

users = ArrayList<Any?>()

इसके बाद, init ब्लॉक कुछ ऐसा दिखेगा:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")

    users.add(user1)
    users.add(user2)
    users.add(user3)
}

इन बदलावों के बाद, हमारी users प्रॉपर्टी अब शून्य नहीं है. साथ ही, हम !! ऑपरेटर की सभी ग़ैर-ज़रूरी घटनाओं को हटा सकते हैं. ध्यान दें कि आपको Android Studio में अब भी कंपाइल करने से जुड़ी गड़बड़ियां दिखेंगी. हालांकि, उन्हें ठीक करने के लिए, कोडलैब के अगले कुछ चरणों को पूरा करें.

val userNames: MutableList<String?> = ArrayList(users.size)
for (user in users) {
    var name: String
    name = if (user.lastName != null) {
        if (user.firstName != null) {
            user.firstName + " " + user.lastName
        } else {
            user.lastName
        }
    } else if (user.firstName != null) {
        user.firstName
    } else {
        "Unknown"
    }
    userNames.add(name)
}

साथ ही, अगर आपने userNames वैल्यू के लिए, ArrayList के टाइप को Strings के तौर पर बताया है, तो एलान में साफ़ तौर पर टाइप हटाया जा सकता है, क्योंकि यह अनुमान लगाया जाएगा.

val userNames = ArrayList<String>(users.size)

स्ट्रक्चर हटाना

Kotlin, डस्ट्रक्चरिंग डिक्लेरेशन नाम के सिंटैक्स का इस्तेमाल करके, किसी ऑब्जेक्ट को कई वैरिएबल में बांटने की सुविधा देता है. हम कई वैरिएबल बनाते हैं और उनका अलग-अलग इस्तेमाल कर सकते हैं.

उदाहरण के लिए, data क्लास में डेस्ट्रक्चर करने की सुविधा होती है, ताकि हम for लूप में मौजूद User ऑब्जेक्ट को (firstName, lastName) में डेस्ट्रक्चर कर सकें. इससे, हम सीधे firstName और lastName वैल्यू के साथ काम कर सकते हैं. for लूप को नीचे दिखाए गए तरीके से अपडेट करें. user.firstName के सभी इंस्टेंस को firstName से बदलें और user.lastName को lastName से बदलें.

for ((firstName, lastName) in users) {
    var name: String
    name = if (lastName != null) {
        if (firstName != null) {
            firstName + " " + lastName
        } else {
            lastName
        }
    } else if (firstName != null) {
        firstName
    } else {
        "Unknown"
    }
    userNames.add(name)
}

if एक्सप्रेशन

userNames की सूची में मौजूद नाम, अभी तक हमारे पसंदीदा फ़ॉर्मैट में नहीं हैं. lastName और firstName, दोनों null हो सकते हैं. इसलिए, फ़ॉर्मैट किए गए उपयोगकर्ता नामों की सूची बनाते समय, हमें यह मैनेज करना होगा कि कोई वैल्यू मौजूद है या नहीं. अगर कोई भी नाम मौजूद नहीं है, तो हम "Unknown" दिखाना चाहते हैं. name वैरिएबल को एक बार सेट करने के बाद, उसमें बदलाव नहीं किया जा सकेगा. इसलिए, var के बजाय val का इस्तेमाल किया जा सकता है. पहले यह बदलाव करें.

val name: String

नाम वैरिएबल सेट करने वाले कोड पर एक नज़र डालें. आपको यह देखकर शायद हैरानी हो कि किसी वैरिएबल को कोड के if / else ब्लॉक के बराबर सेट किया जा रहा है. ऐसा इसलिए किया जा सकता है, क्योंकि Kotlin में if और when एक्सप्रेशन हैं—ये वैल्यू दिखाते हैं. if स्टेटमेंट की आखिरी लाइन को name को असाइन किया जाएगा. इस ब्लॉक का सिर्फ़ एक मकसद है, name वैल्यू को शुरू करना.

यहां दिया गया लॉजिक यह है कि अगर lastName शून्य है, तो name को firstName या "Unknown" पर सेट किया जाता है.

name = if (lastName != null) {
    if (firstName != null) {
        firstName + " " + lastName
    } else {
        lastName
    }
} else if (firstName != null) {
    firstName
} else {
    "Unknown"
}

एल्विस ऑपरेटर

elvis ऑपरेटर ?: का इस्तेमाल करके, इस कोड को और आसानी से लिखा जा सकता है. अगर बाईं ओर मौजूद एक्सप्रेशन शून्य नहीं है, तो एल्विस ऑपरेटर बाईं ओर मौजूद एक्सप्रेशन दिखाएगा. अगर बाईं ओर मौजूद एक्सप्रेशन शून्य है, तो एल्विस ऑपरेटर दाईं ओर मौजूद एक्सप्रेशन दिखाएगा.

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

name = if (lastName != null) {
    ...
} else {
    firstName ?: "Unknown"
}

7. स्ट्रिंग टेंप्लेट

Kotlin में स्ट्रिंग टेंप्लेट की मदद से, String के साथ आसानी से काम किया जा सकता है. स्ट्रिंग टेंप्लेट की मदद से, स्ट्रिंग के अंदर किए गए एलान के वैरिएबल का रेफ़रंस लिया जा सकता है. इसके लिए, वैरिएबल से पहले $ सिंबल का इस्तेमाल करें. एक्सप्रेशन को स्ट्रिंग के एलान में भी डाला जा सकता है. इसके लिए, एक्सप्रेशन को { } के अंदर रखें और उसके पहले $ सिंबल का इस्तेमाल करें. उदाहरण: ${user.firstName}.

फ़िलहाल, आपका कोड उपयोगकर्ता नाम में firstName और lastName को जोड़ने के लिए, स्ट्रिंग को जोड़ने की सुविधा का इस्तेमाल करता है.

if (firstName != null) {
    firstName + " " + lastName
}

इसके बजाय, स्ट्रिंग को जोड़ने की सुविधा को इनसे बदलें:

if (firstName != null) {
    "$firstName $lastName"
}

स्ट्रिंग टेंप्लेट का इस्तेमाल करके, अपने कोड को आसान बनाया जा सकता है.

अगर कोड लिखने का कोई और तरीका है, तो आपका आईडीई आपको चेतावनियां दिखाएगा. आपको कोड में, टेढ़े-मेढ़े अंडरलाइन दिखेंगे. इन पर कर्सर घुमाने पर, आपको कोड को फिर से लिखने का सुझाव दिखेगा.

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

val formattedUserNames: List<String?>
    get() {
        val userNames = ArrayList<String>(users.size)
        for ((firstName, lastName) in users) {
            val name = if (lastName != null) {
                if (firstName != null) {
                    "$firstName $lastName"
                } else {
                    lastName
                }
            } else {
                firstName ?: "Unknown"
            }
            userNames.add(name)
        }
        return userNames
    }

हम एक और बदलाव कर सकते हैं. अगर कोई व्यक्ति अपने नाम की जानकारी नहीं देता है, तो हमारा यूज़र इंटरफ़ेस (यूआई) लॉजिक "Unknown" दिखाता है. इसलिए, हम शून्य ऑब्जेक्ट का इस्तेमाल नहीं कर रहे हैं. इसलिए, formattedUserNames के डेटा टाइप के लिए, List<String?> को List<String> से बदलें.

val formattedUserNames: List<String>

8. कलेक्शन पर होने वाले ऑपरेशन

आइए, formattedUserNames गटर पर नज़र डालते हैं और देखते हैं कि इसे कैसे और बेहतर बनाया जा सकता है. फ़िलहाल, कोड ये काम करता है:

  • स्ट्रिंग की नई सूची बनाता है
  • उपयोगकर्ताओं की सूची में मौजूद हर उपयोगकर्ता के लिए
  • उपयोगकर्ता के नाम और उपनाम के आधार पर, हर उपयोगकर्ता के लिए फ़ॉर्मैट किया गया नाम बनाता है
  • नई सूची दिखाता है
    val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users.size)
            for ((firstName, lastName) in users) {
                val name = if (lastName != null) {
                    if (firstName != null) {
                        "$firstName $lastName"
                    } else {
                        lastName
                    }
                } else {
                    firstName ?: "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

Kotlin में कलेक्शन ट्रांसफ़ॉर्मेशन की एक बड़ी सूची उपलब्ध होती है. इससे Java Collections API की सुविधाओं को बढ़ाकर, डेवलपमेंट को तेज़ और सुरक्षित बनाया जा सकता है. इनमें से एक map फ़ंक्शन है. यह फ़ंक्शन एक नई सूची दिखाता है. इसमें, ओरिजनल सूची के हर एलिमेंट पर ट्रांसफ़ॉर्म फ़ंक्शन लागू करने के नतीजे शामिल होते हैं. इसलिए, नई सूची बनाने और उपयोगकर्ताओं की सूची को मैन्युअल तरीके से दोहराने के बजाय, हम map फ़ंक्शन का इस्तेमाल कर सकते हैं. साथ ही, for लूप में मौजूद लॉजिक को map बॉडी में ले जा सकते हैं. डिफ़ॉल्ट रूप से, map में इस्तेमाल किए गए मौजूदा सूची आइटम का नाम it होता है. हालांकि, इसे आसानी से पढ़ने के लिए, it को अपने वैरिएबल के नाम से बदला जा सकता है. हमारे मामले में, इसे user नाम दें:

val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                val name = if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
                name
            }
        }

ध्यान दें कि अगर user.lastName शून्य है, तो हम "Unknown" दिखाने के लिए Elvis ऑपरेटर का इस्तेमाल करते हैं. इसकी वजह यह है कि user.lastName String? टाइप का है और name के लिए String ज़रूरी है.

...
else {
    user.lastName ?: "Unknown"
}
...

इसे और भी आसान बनाने के लिए, हम name वैरिएबल को पूरी तरह से हटा सकते हैं:

val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

9. प्रॉपर्टी और बैकिंग प्रॉपर्टी

हमने देखा कि अपने-आप बदलने वाले कन्वर्टर ने getFormattedUserNames() फ़ंक्शन को formattedUserNames नाम की ऐसी प्रॉपर्टी से बदल दिया है जिसमें कस्टम गटर है. हालांकि, Kotlin अब भी getFormattedUserNames() मेथड जनरेट करता है, जो List दिखाता है.

Java में, हम अपनी क्लास प्रॉपर्टी को गेट्टर और सेटर फ़ंक्शन के ज़रिए एक्सपोज़ करते हैं. Kotlin की मदद से, फ़ील्ड की मदद से बताई गई किसी क्लास की प्रॉपर्टी और फ़ंक्शन की मदद से बताई गई किसी क्लास की सुविधाओं और कार्रवाइयों के बीच बेहतर तरीके से अंतर किया जा सकता है. हमारे मामले में, Repository क्लास बहुत आसान है और कोई कार्रवाई नहीं करती है, इसलिए इसमें सिर्फ़ फ़ील्ड हैं.

Java getFormattedUserNames() फ़ंक्शन में ट्रिगर किया गया लॉजिक, अब formattedUserNames Kotlin प्रॉपर्टी के गेट्टर को कॉल करते समय ट्रिगर होता है.

हमारे पास formattedUserNames प्रॉपर्टी से जुड़ा कोई फ़ील्ड नहीं है. हालांकि, Kotlin हमें field नाम का एक अपने-आप बैकअप लेने वाला फ़ील्ड देता है. ज़रूरत पड़ने पर, हम कस्टम गेट्टर और सेटर से इसे ऐक्सेस कर सकते हैं.

हालांकि, कभी-कभी हमें कुछ ऐसी अतिरिक्त सुविधाएं चाहिए जो अपने-आप बैकअप लेने वाले फ़ील्ड में उपलब्ध नहीं होतीं.

आइए, एक उदाहरण से इसे समझते हैं.

हमारी Repository क्लास में, उपयोगकर्ताओं की ऐसी सूची होती है जिसमें बदलाव किया जा सकता है. यह सूची, getUsers() फ़ंक्शन में दिखती है. यह फ़ंक्शन, हमारे Java कोड से जनरेट होता है:

fun getUsers(): List<User>? {
    return users
}

हम नहीं चाहते थे कि Repository क्लास के कॉलर, उपयोगकर्ताओं की सूची में बदलाव करें. इसलिए, हमने getUsers() फ़ंक्शन बनाया, जो सिर्फ़ पढ़ने के लिए उपलब्ध List<User> दिखाता है. Kotlin में, हम ऐसे मामलों में फ़ंक्शन के बजाय प्रॉपर्टी का इस्तेमाल करना पसंद करते हैं. ज़्यादा सटीक तरीके से, हम सिर्फ़ पढ़ने के लिए उपलब्ध List<User> दिखाएंगे, जिसका बैकअप mutableListOf<User> है.

सबसे पहले, users का नाम बदलकर _users कर दें. वैरिएबल के नाम को हाइलाइट करें. इसके बाद, वैरिएबल को फिर से फ़ैक्टर करें > नाम बदलें के लिए, राइट क्लिक करें. इसके बाद, उपयोगकर्ताओं की सूची दिखाने वाली, सिर्फ़ पढ़ने के लिए उपलब्ध पब्लिक प्रॉपर्टी जोड़ें. इसे users कहें:

private val _users = mutableListOf<User>()
val users: List<User>
    get() = _users

इसके बाद, getUsers() का तरीका मिटाया जा सकता है.

ऊपर दिए गए बदलाव के बाद, निजी _users प्रॉपर्टी, सार्वजनिक users प्रॉपर्टी की बैकिंग प्रॉपर्टी बन जाती है. Repository क्लास के बाहर, _users सूची में बदलाव नहीं किया जा सकता. इसकी वजह यह है कि क्लास के उपभोक्ता, सूची को सिर्फ़ users के ज़रिए ऐक्सेस कर सकते हैं.

जब Kotlin कोड से users को कॉल किया जाता है, तो Kotlin स्टैंडर्ड लाइब्रेरी में मौजूद List का इस्तेमाल किया जाता है. इस लाइब्रेरी में मौजूद सूची में बदलाव नहीं किया जा सकता. अगर users को Java से कॉल किया जाता है, तो java.util.List लागू करने का तरीका इस्तेमाल किया जाता है. इस तरीके में, सूची में बदलाव किया जा सकता है और add() और remove() जैसे ऑपरेशन उपलब्ध होते हैं.

पूरा कोड:

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

10. टॉप-लेवल और एक्सटेंशन फ़ंक्शन और प्रॉपर्टी

फ़िलहाल, Repository क्लास को User ऑब्जेक्ट के लिए, फ़ॉर्मैट किए गए उपयोगकर्ता नाम का हिसाब लगाने का तरीका पता है. हालांकि, अगर हमें अन्य क्लास में फ़ॉर्मैटिंग के उसी लॉजिक का फिर से इस्तेमाल करना है, तो हमें उसे कॉपी करके चिपकाना होगा या User क्लास में ले जाना होगा.

Kotlin में, किसी क्लास, ऑब्जेक्ट या इंटरफ़ेस के बाहर फ़ंक्शन और प्रॉपर्टी का एलान किया जा सकता है. उदाहरण के लिए, List का नया इंस्टेंस बनाने के लिए इस्तेमाल किया गया mutableListOf() फ़ंक्शन, Kotlin स्टैंडर्ड लाइब्रेरी के Collections.kt में पहले से ही तय है.

Java में, जब भी आपको कोई यूटिलिटी फ़ंक्शन चाहिए, तो ज़्यादातर मामलों में आपके पास Util क्लास बनाने और उस फ़ंक्शन को स्टैटिक फ़ंक्शन के तौर पर एलान करने का विकल्प होता है. Kotlin में, क्लास के बिना भी टॉप-लेवल फ़ंक्शन का एलान किया जा सकता है. हालांकि, Kotlin में एक्सटेंशन फ़ंक्शन बनाने की सुविधा भी मिलती है. ये ऐसे फ़ंक्शन होते हैं जो किसी खास टाइप को बढ़ाते हैं, लेकिन उन्हें टाइप के बाहर घोषित किया जाता है.

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

User क्लास के लिए, हम फ़ॉर्मैट किए गए नाम का हिसाब लगाने वाला एक्सटेंशन फ़ंक्शन जोड़ सकते हैं या फ़ॉर्मैट किए गए नाम को एक्सटेंशन प्रॉपर्टी में सेव कर सकते हैं. इसे Repository क्लास के बाहर, उसी फ़ाइल में जोड़ा जा सकता है:

// extension function
fun User.getFormattedName(): String {
    return if (lastName != null) {
        if (firstName != null) {
            "$firstName $lastName"
        } else {
            lastName ?: "Unknown"
        }
    } else {
        firstName ?: "Unknown"
    }
}

// extension property
val User.userFormattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName

इसके बाद, हम एक्सटेंशन फ़ंक्शन और प्रॉपर्टी का इस्तेमाल इस तरह कर सकते हैं जैसे वे User क्लास का हिस्सा हों.

फ़ॉर्मैट किया गया नाम, User क्लास की प्रॉपर्टी है, न कि Repository क्लास की कोई सुविधा. इसलिए, एक्सटेंशन प्रॉपर्टी का इस्तेमाल करें. हमारी Repository फ़ाइल अब इस तरह दिखती है:

val User.formattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
      get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user -> user.formattedName }
        }

    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

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

11. स्कोप फ़ंक्शन: let, apply, with, run, also

हम अपने Repository क्लास कोड में, _users सूची में कई User ऑब्जेक्ट जोड़ रहे हैं. Kotlin स्कोप फ़ंक्शन की मदद से, इन कॉल को ज़्यादा आसान बनाया जा सकता है.

Kotlin में पांच स्कोप फ़ंक्शन उपलब्ध हैं: let, apply, with, run, और also. इनका इस्तेमाल करके, किसी ऑब्जेक्ट को उसके नाम के आधार पर ऐक्सेस किए बिना, सिर्फ़ उस ऑब्जेक्ट के संदर्भ में कोड को चलाया जा सकता है. इन फ़ंक्शन की मदद से, आपके कोड को आसानी से पढ़ा जा सकता है और उसे छोटा किया जा सकता है. सभी स्कोप फ़ंक्शन में एक रिसीवर (this) होता है. इनमें कोई आर्ग्युमेंट (it) हो सकता है और ये कोई वैल्यू दिखा सकते हैं.

यहां एक आसान चैट शीट दी गई है, ताकि आपको यह याद रहे कि हर फ़ंक्शन का इस्तेमाल कब करना है:

6b9283d411fb6e7b.png

हम Repository में अपने _users ऑब्जेक्ट को कॉन्फ़िगर कर रहे हैं. इसलिए, apply फ़ंक्शन का इस्तेमाल करके, कोड को ज़्यादा आसान बनाया जा सकता है:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")
   
    _users.apply {
       // this == _users
       add(user1)
       add(user2)
       add(user3)
    }
 }

12. आखिर में खास जानकारी

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

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

private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))

हमने कई विषयों पर बात की है. जैसे, वैल्यू न होने की स्थिति को मैनेज करना, सिंगलटन, स्ट्रिंग, और कलेक्शन से लेकर एक्सटेंशन फ़ंक्शन, टॉप-लेवल फ़ंक्शन, प्रॉपर्टी, और स्कोप फ़ंक्शन जैसे विषय. हमने दो Java क्लास को दो Kotlin क्लास में बदल दिया है, जो अब इस तरह दिखती हैं:

User.kt

data class User(var firstName: String?, var lastName: String?)

Repository.kt

val User.formattedName: String
    get() {
       return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() = _users.map { user -> user.formattedName }
}

यहां Java की सुविधाओं और Kotlin में उनकी मैपिंग के बारे में कम शब्दों में बताया गया है:

Java

Kotlin

final ऑब्जेक्ट

val ऑब्जेक्ट

equals()

==

==

===

सिर्फ़ डेटा सेव करने वाली क्लास

data क्लास

कंस्ट्रक्टर में शुरू करना

init ब्लॉक में डेटा लेयर में इवेंट बनाने की प्रोसेस

static फ़ील्ड और फ़ंक्शन

companion object में बताए गए फ़ील्ड और फ़ंक्शन

सिंगलटन क्लास

object

Kotlin और अपने प्लैटफ़ॉर्म पर इसका इस्तेमाल करने के तरीके के बारे में ज़्यादा जानने के लिए, ये संसाधन देखें: