Kotlin में रीफ़ैक्टर करना

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

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

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

आपको Java को Kotlin में बदलने का तरीका बताया जाएगा. ऐसा करने से, आपको Kotlin लैंग्वेज की इन सुविधाओं और सिद्धांतों के बारे में जानने को मिलेगा:

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

अनुमान

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

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

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

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

अगर Android Studio का इस्तेमाल किया जा रहा है, तो बिना किसी ऐक्टिविटी वाला नया प्रोजेक्ट बनाएं.

कोड

हम एक 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;
    }

}

अपने प्रोजेक्ट के टाइप के हिसाब से, अगर Android प्रोजेक्ट का इस्तेमाल किया जा रहा है, तो androidx.annotation.Nullable इंपोर्ट करें. अगर किसी और प्रोजेक्ट का इस्तेमाल किया जा रहा है, तो 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;
    }
}

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

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

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

आपको यह 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 में भी अपने-आप कई कंस्ट्रक्टर बना देगा. इन्हें constructor कीवर्ड का इस्तेमाल करके तय किया जाता है.

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

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

Equality

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

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

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

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

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("John", "Doe")

फ़ंक्शन कॉल करते समय, फ़ंक्शन के पैरामीटर को नाम दिया जा सकता है:

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")

आगे बढ़ने से पहले, पक्का करें कि आपकी 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)
   }
}

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

  • एक init ब्लॉक जोड़ा गया (Repository.kt#L50)
  • static फ़ील्ड अब companion object ब्लॉक (Repository.kt#L33) का हिस्सा है
  • users की सूची में नल वैल्यू हो सकती है, क्योंकि ऑब्जेक्ट को एलान के समय इंस्टैंटिएट नहीं किया गया था (Repository.kt#L7)
  • getFormattedUserNames() तरीका अब formattedUserNames (Repository.kt#L11) नाम की प्रॉपर्टी है
  • उपयोगकर्ताओं की सूची (जो शुरू में getFormattedUserNames( का हिस्सा थी) पर दोहराव का सिंटैक्स, Java के सिंटैक्स (Repository.kt#L15) से अलग है

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

  • users टाइप के एलान में, User? के अंदर मौजूद ? हटाएं
  • getUsers को List<User>? वापस करना होगा

ऑटो-कनवर्टर ने, उपयोगकर्ता के वैरिएबल और init ब्लॉक में तय किए गए वैरिएबल के एलान को भी दो लाइनों में बांट दिया है. आइए, हर वैरिएबल डिक्लेरेशन को एक लाइन में रखते हैं. हमारा कोड ऐसा दिखना चाहिए:

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)
    }
}

Init block

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 वर्शन में, हम देखते हैं कि users प्रॉपर्टी को डिक्लेरेशन में शुरू किया गया था.

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 users = Repository.users

Destructuring

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

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

 
for ((firstName, lastName) in users!!) {
       val name: String?

       if (lastName != null) {
          if (firstName != null) {
                name = "$firstName $lastName"
          } else {
                name = lastName
          }
       } else if (firstName != null) {
            name = firstName
       } else {
            name = "Unknown"
       }
       userNames.add(name)
}

Repository क्लास को Kotlin में बदलते समय, ऑटोमैटिक कन्वर्टर ने उपयोगकर्ताओं की सूची को नल वैल्यू असाइन करने की अनुमति दी. ऐसा इसलिए, क्योंकि इसे ऑब्जेक्ट के तौर पर तब शुरू नहीं किया गया था, जब इसे घोषित किया गया था. users ऑब्जेक्ट के सभी इस्तेमाल के लिए, not-null assertion ऑपरेटर !! का इस्तेमाल किया जाता है. यह किसी भी वैरिएबल को गैर-शून्य टाइप में बदल देता है और वैल्यू के शून्य होने पर अपवाद देता है. !! का इस्तेमाल करने पर, रनटाइम के दौरान अपवादों के होने का खतरा होता है.

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

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

हमारे मामले में, हमें पता है कि उपयोगकर्ताओं की सूची को नल नहीं किया जाना चाहिए, क्योंकि ऑब्जेक्ट बनने के तुरंत बाद इसे शुरू किया जाता है. इसलिए, इसे घोषित करते समय हम सीधे तौर पर ऑब्जेक्ट को इंस्टैंटिएट कर सकते हैं.

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

private var users: MutableList<User>? = null

आसान बनाने के लिए, हम mutableListOf() फ़ंक्शन का इस्तेमाल कर सकते हैं. साथ ही, सूची के एलिमेंट का टाइप दे सकते हैं. इसके अलावा, init ब्लॉक से ArrayList कंस्ट्रक्टर कॉल को हटाया जा सकता है और users प्रॉपर्टी के टाइप का एलान भी हटाया जा सकता है.

private val users = mutableListOf<User>()

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

इन बदलावों के बाद, हमारी users प्रॉपर्टी अब नॉन-नल है. साथ ही, हम !! ऑपरेटर के सभी गैर-ज़रूरी उदाहरणों को हटा सकते हैं.

val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
    ...
}

साथ ही, उपयोगकर्ताओं के वैरिएबल को पहले ही शुरू कर दिया गया है. इसलिए, हमें 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)
}

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

val name: String

अगर lastName शून्य है, तो name, firstName या "Unknown" में से कोई एक होगा:

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

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

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

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

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

ऑटोमैटिक कन्वर्टर ने, पहले और आखिरी नाम को जोड़ने की प्रोसेस को अपडेट किया है. इससे, स्ट्रिंग में वैरिएबल के नाम को सीधे तौर पर रेफ़रंस किया जा सकता है. इसके लिए, $ सिंबल का इस्तेमाल किया जाता है और एक्सप्रेशन को { } के बीच में रखा जाता है.

// Java
name = user.getFirstName() + " " + user.getLastName();

// Kotlin
name = "${user.firstName} ${user.lastName}"

कोड में, स्ट्रिंग कॉनकैटेनेशन को इससे बदलें:

name = "$firstName $lastName"

Kotlin में if, when, for, और while एक्सप्रेशन हैं. ये वैल्यू को रिटर्न करते हैं. आपके आईडीई में यह चेतावनी भी दिख रही है कि असाइनमेंट को if से बाहर ले जाना चाहिए:

आइए, आईडीई के सुझाव का पालन करें और दोनों if स्टेटमेंट के लिए असाइनमेंट हटा दें. if स्टेटमेंट की आखिरी लाइन असाइन की जाएगी. इस तरह, यह साफ़ तौर पर पता चलता है कि इस ब्लॉक का मकसद सिर्फ़ नाम की वैल्यू को शुरू करना है:

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

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

val formattedUserNames: List<String?>
   get() {
       val userNames: MutableList<String?> = ArrayList(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
   }

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

  • यह फ़ंक्शन, स्ट्रिंग की नई सूची बनाता है
  • यह कुकी, उपयोगकर्ताओं की सूची में बदलाव करती है
  • यह कुकी, उपयोगकर्ता के नाम और उपनाम के आधार पर, हर उपयोगकर्ता के लिए फ़ॉर्मैट किया गया नाम बनाती है
  • नई बनाई गई सूची दिखाता है
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
            }
        }

इसे और भी आसान बनाने के लिए, हम 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"
                }
            }
        }

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

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

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

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

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

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

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

यहां समस्या यह है कि users को वापस लाने पर, Repository क्लास का कोई भी उपभोक्ता, उपयोगकर्ताओं की हमारी सूची में बदलाव कर सकता है. यह सही नहीं है! आइए, बैकिंग प्रॉपर्टी का इस्तेमाल करके इस समस्या को ठीक करें.

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

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

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

पूरा कोड:

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)
    }
}

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

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

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 क्लास का हिस्सा हों.

फ़ॉर्मैट किया गया नाम, उपयोगकर्ता की प्रॉपर्टी है. यह 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 Standard Library, कई Java एपीआई की सुविधाओं को बढ़ाने के लिए एक्सटेंशन फ़ंक्शन का इस्तेमाल करती है. Iterable और Collection की कई सुविधाएं, एक्सटेंशन फ़ंक्शन के तौर पर लागू की जाती हैं. उदाहरण के लिए, पिछले चरण में इस्तेमाल किया गया map फ़ंक्शन, Iterable पर एक एक्सटेंशन फ़ंक्शन है.

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

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

यहां एक काम की चीट शीट दी गई है, ताकि आपको यह याद रखने में आसानी हो:

हम 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)
    }
 }

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

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

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

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

User.kt

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 के बारे में ज़्यादा जानने और इसे अपने प्लैटफ़ॉर्म पर इस्तेमाल करने का तरीका जानने के लिए, ये संसाधन देखें: