Kotlin को रीफ़ैक्टर करना

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

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

आप इन चीज़ों के बारे में जानेंगे

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

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

अनुमान

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

आपको इनकी ज़रूरत होगी

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

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

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

कोड

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

ऐप्लिकेशन/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;
    }
}

हमारे आईडीई से Java कोड को अपने-आप Kotlin कोड में बदलने का काम किया जा सकता है, लेकिन कभी-कभी इसमें थोड़ी मदद की ज़रूरत होती है. हम इसे पहले #39; और फिर रीफ़ैक्टर कोड से करते हैं, ताकि यह समझने में मदद मिल सके कि इस तरीके को कैसे और क्यों बेहतर बनाया गया है.

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

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

आपको नीचे दिया गया 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 के बीच एक मुख्य अंतर यह है कि यह साफ़ तौर पर बताता है कि कोई वैरिएबल शून्य वैल्यू को स्वीकार कर सकता है या नहीं. यह टाइप एलान में एक `?` जोड़कर उसे लागू करता है.

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

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

डेटा क्लास

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

हमारी User कक्षा में data कीवर्ड जोड़ने दें:

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

Java की तरह, Kotlin में एक मुख्य कंस्ट्रक्टर और एक या ज़्यादा सेकंडरी कंस्ट्रक्टर हो सकते हैं. ऊपर दिए गए उदाहरण में, यूज़र क्लास का मुख्य कंस्ट्रक्टर है. अगर आप एक ऐसे Java क्लास को बदल रहे हैं जिसमें कई कंस्ट्रक्टर हैं, तो कन्वर्टर, 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

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

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

इसे एक अलग इस्तेमाल के तौर पर, मान लें कि #39; में 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 क्लास है. Let's से 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 सूची को एक बदली जा सकने वाली सूची बनाई है, जिसमें शून्य होने वाले ऑब्जेक्ट मौजूद हैं. हालांकि, सूची शून्य हो सकती है, लेकिन मान लें कि इसमें #39;शून्य उपयोगकर्ता नहीं हो सकते. इसलिए, ये करें:

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

इनिट ब्लॉक

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's 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

खराब किया जा रहा है

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

उदाहरण के लिए, डेटा क्लास को नुकसान पहुंचाने वाला मोड काम करता है, ताकि हम for लूप में मौजूद User ऑब्जेक्ट को (firstName, lastName) में बदल सकें. इससे हम सीधे firstName और lastName वैल्यू के साथ काम कर सकते हैं. for को 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 ऑब्जेक्ट के सभी इस्तेमाल के लिए, गैर-शून्य के दावे वाले ऑपरेटर !! का इस्तेमाल किया जाता है. यह किसी भी वैरिएबल को गैर-शून्य टाइप में बदल देता है और वैल्यू के शून्य होने पर अपवाद देता है. !! का इस्तेमाल करके, आप रनटाइम पर रनटाइम में अपवादों के जोखिम में पड़ जाते हैं.

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

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

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

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

private var users: MutableList<User>? = null

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

private val users = mutableListOf<User>()

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

इन बदलावों के साथ, हमारी 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 हो सकते हैं. इसलिए, जब हम फ़ॉर्मैट किए गए उपयोगकर्ता नामों की सूची बनाते हैं, तो हमें कुछ नहीं होने चाहिए. अगर दोनों में से कोई भी नाम मौजूद नहीं है, तो हम "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 के साथ काम करना आसान बनाती है. स्ट्रिंग टेंप्लेट की मदद से, स्ट्रिंग के अंदर किए गए एलान के वैरिएबल का रेफ़रंस लें.

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

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

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

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

name = "$firstName $lastName"

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

IDE's के सुझाव को फ़ॉलो करें और 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 को बेहतर तरीके से जानें और देखें कि हम इसे और बेहतर कैसे बना सकते हैं. फ़िलहाल, कोड ये काम करता है:

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

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

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

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

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

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

यहां समस्या यह है कि users को रिपॉज़िटरी क्लास का कोई भी उपभोक्ता इस्तेमाल करके, उपयोगकर्ताओं की हमारी सूची में बदलाव कर सकता है - अच्छा सुझाव नहीं है! बैक अप प्रॉपर्टी का इस्तेमाल करके, इसे ठीक करें.

पहले, 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() का एक नया इंस्टेंस बनाने के लिए इस्तेमाल किया गया List फ़ंक्शन, सीधे Collections.kt Standard लाइब्रेरी में बताया गया है.

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 स्टैंडर्ड लाइब्रेरी, 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 में बदलना शुरू कर सकते हैं. रीफ़ैक्टर करने की यह सुविधा आपके डेवलपमेंट प्लैटफ़ॉर्म से अलग है और यह पक्का करने में मदद करती है कि आप जो कोड लिखते हैं वह मुहावरेदार हो.

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

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 फ़ंक्शन की TL;DR और Kotlin में उसकी मैपिंग की जानकारी दी गई है:

Java

कोटलिन

final ऑब्जेक्ट

val ऑब्जेक्ट

equals()

==

==

===

वह क्लास जिसमें सिर्फ़ डेटा होता है

data कक्षा

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

init ब्लॉक में शुरू किया गया

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

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

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

object

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