কোটলিনে রিফ্যাক্টরিং

এই কোডল্যাবে, আপনি কীভাবে আপনার কোড জাভা থেকে কোটলিনে রূপান্তর করবেন তা শিখবেন। আপনি কোটলিন ভাষার কনভেনশনগুলি কী এবং আপনি যে কোডটি লিখছেন তা সেগুলি অনুসরণ করে তা কীভাবে নিশ্চিত করবেন তাও শিখবেন৷

এই কোডল্যাবটি জাভা ব্যবহার করে এমন যেকোন ডেভেলপারের জন্য উপযুক্ত যারা তাদের প্রোজেক্ট কোটলিনে স্থানান্তরিত করার কথা ভাবছেন। আমরা কয়েকটি জাভা ক্লাস দিয়ে শুরু করব যা আপনি IDE ব্যবহার করে Kotlin-এ রূপান্তর করবেন। তারপরে আমরা রূপান্তরিত কোডটি একবার দেখব এবং দেখব কিভাবে আমরা এটিকে আরও বাহাদুরী করে উন্নত করতে পারি এবং সাধারণ সমস্যাগুলি এড়াতে পারি৷

আপনি কি শিখবেন

আপনি শিখবেন কিভাবে জাভাকে কোটলিনে রূপান্তর করতে হয়। এটি করার মাধ্যমে আপনি নিম্নলিখিত কোটলিন ভাষার বৈশিষ্ট্য এবং ধারণাগুলি শিখবেন:

  • শূন্যতা হ্যান্ডলিং
  • সিঙ্গেলটন বাস্তবায়ন করা
  • ডেটা ক্লাস
  • হ্যান্ডলিং স্ট্রিং
  • এলভিস অপারেটর
  • ধ্বংস করা
  • বৈশিষ্ট্য এবং ব্যাকিং বৈশিষ্ট্য
  • ডিফল্ট আর্গুমেন্ট এবং নাম পরামিতি
  • সংগ্রহ নিয়ে কাজ করা
  • এক্সটেনশন ফাংশন
  • শীর্ষ-স্তরের ফাংশন এবং পরামিতি
  • let , apply করুন, with , এবং কীওয়ার্ড run

অনুমান

আপনি ইতিমধ্যে জাভা সঙ্গে পরিচিত হওয়া উচিত.

আপনি কি প্রয়োজন হবে

একটি নতুন প্রকল্প তৈরি করুন

আপনি যদি IntelliJ IDEA ব্যবহার করেন, Kotlin/JVM এর সাথে একটি নতুন জাভা প্রকল্প তৈরি করুন।

আপনি যদি অ্যান্ড্রয়েড স্টুডিও ব্যবহার করেন, তাহলে কোনো কার্যকলাপ ছাড়াই একটি নতুন প্রকল্প তৈরি করুন।

কোড

আমরা একটি 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 স্বয়ংক্রিয়ভাবে জাভা কোডকে Kotlin কোডে রিফ্যাক্টর করার জন্য একটি সুন্দর কাজ করতে পারে তবে কখনও কখনও এটির একটু সাহায্যের প্রয়োজন হয়। আমরা প্রথমে এটি করব এবং তারপরে কীভাবে এবং কেন এইভাবে রিফ্যাক্টর করা হয়েছে তা বোঝার জন্য রিফ্যাক্টর কোডের মধ্য দিয়ে যাব।

User.java ফাইলে যান এবং এটিকে Kotlin এ রূপান্তর করুন: মেনু বার -> কোড -> Java ফাইলকে Kotlin ফাইলে রূপান্তর করুন।

যদি আপনার IDE রূপান্তরের পরে সংশোধন করার জন্য অনুরোধ করে, হ্যাঁ টিপুন।

আপনি নিম্নলিখিত Kotlin কোড দেখতে হবে: :

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

মনে রাখবেন User.java এর নাম পরিবর্তন করে User.kt করা হয়েছে। Kotlin ফাইলের এক্সটেনশন আছে .kt.

আমাদের জাভা User ক্লাসে আমাদের দুটি বৈশিষ্ট্য ছিল: firstName এবং lastName । প্রতিটির একটি গেটার এবং সেটার পদ্ধতি ছিল, যার ফলে এটির মান পরিবর্তনযোগ্য ছিল। মিউটেবল ভেরিয়েবলের জন্য কোটলিনের কীওয়ার্ড হল var , তাই রূপান্তরকারী এই বৈশিষ্ট্যগুলির প্রতিটির জন্য var ব্যবহার করে। যদি আমাদের জাভা বৈশিষ্ট্যে শুধুমাত্র গেটার থাকে, তাহলে সেগুলি অপরিবর্তনীয় হবে এবং val ভেরিয়েবল হিসেবে ঘোষণা করা হবে। val জাভাতে final কীওয়ার্ডের মতো।

কোটলিন এবং জাভার মধ্যে একটি মূল পার্থক্য হল কোটলিন স্পষ্টভাবে নির্দিষ্ট করে যে একটি ভেরিয়েবল একটি নাল মান গ্রহণ করতে পারে কিনা। এটি একটি ` যুক্ত করে এটি করে ? ` টাইপ ঘোষণার জন্য।

যেহেতু আমরা firstName এবং lastName nullable হিসেবে চিহ্নিত করেছি, অটো-কনভার্টার স্বয়ংক্রিয়ভাবে বৈশিষ্ট্যগুলিকে String? . আপনি যদি আপনার জাভা সদস্যদের নন-নাল হিসেবে ( org.jetbrains.annotations.NotNull বা androidx.annotation.NonNull ব্যবহার করে) টীকা দেন, তাহলে রূপান্তরকারী এটিকে চিনবে এবং ক্ষেত্রগুলিকে কোটলিনেও নন-নাল করে দেবে।

প্রাথমিক রিফ্যাক্টরিং ইতিমধ্যে সম্পন্ন হয়েছে। তবে আমরা এটি আরও বাজেভাবে লিখতে পারি। চলুন দেখা যাক কিভাবে.

ডেটা ক্লাস

আমাদের User শ্রেণী শুধুমাত্র ডেটা ধারণ করে। কোটলিনের এই ভূমিকা সহ ক্লাসগুলির জন্য একটি কীওয়ার্ড রয়েছে: data । এই ক্লাসটিকে data ক্লাস হিসাবে চিহ্নিত করার মাধ্যমে, কম্পাইলার স্বয়ংক্রিয়ভাবে আমাদের জন্য গেটার এবং সেটার তৈরি করবে। এটি equals() , hashCode() , এবং toString() ফাংশনগুলিও প্রাপ্ত করবে।

আমাদের User ক্লাসে data কীওয়ার্ড যোগ করা যাক:

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

জাভার মতো কোটলিনের একটি প্রাথমিক কনস্ট্রাক্টর এবং এক বা একাধিক সেকেন্ডারি কনস্ট্রাক্টর থাকতে পারে। উপরের উদাহরণে একটি হল ইউজার ক্লাসের প্রাথমিক কনস্ট্রাক্টর। আপনি যদি একাধিক কনস্ট্রাক্টর আছে এমন একটি জাভা ক্লাস রূপান্তর করছেন, তাহলে কনভার্টারটি স্বয়ংক্রিয়ভাবে কোটলিনে একাধিক কনস্ট্রাক্টর তৈরি করবে। তারা constructor কীওয়ার্ড ব্যবহার করে সংজ্ঞায়িত করা হয়।

যদি আমরা এই ক্লাসের একটি উদাহরণ তৈরি করতে চাই, আমরা এটি এভাবে করতে পারি:

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

সমতা

কোটলিনের দুটি ধরণের সমতা রয়েছে:

  • কাঠামোগত সমতা == অপারেটর ব্যবহার করে এবং দুটি দৃষ্টান্ত সমান কিনা তা নির্ধারণ করতে equals() কল করে।
  • রেফারেন্সিয়াল সমতা === অপারেটর ব্যবহার করে এবং দুটি রেফারেন্স একই বস্তুর দিকে নির্দেশ করে কিনা তা পরীক্ষা করে।

ডেটা ক্লাসের প্রাথমিক কনস্ট্রাক্টরে সংজ্ঞায়িত বৈশিষ্ট্যগুলি কাঠামোগত সমতা যাচাইয়ের জন্য ব্যবহার করা হবে।

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

কোটলিনে, আমরা ফাংশন কলে আর্গুমেন্টে ডিফল্ট মান নির্ধারণ করতে পারি। আর্গুমেন্ট বাদ দিলে ডিফল্ট মান ব্যবহার করা হয়। 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") 

একটি ভিন্ন ব্যবহারের ক্ষেত্রে, ধরা যাক যে 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 ক্লাসকে কোটলিনে রূপান্তর করা যাক। স্বয়ংক্রিয় রূপান্তর ফলাফল এই মত হওয়া উচিত:

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( ) এর অংশ ছিল) জাভা ওয়ান (Repository.kt#L15) থেকে আলাদা সিনট্যাক্স রয়েছে

আমরা আরও এগিয়ে যাওয়ার আগে, আসুন কোডটি কিছুটা পরিষ্কার করি। আমরা দেখতে পাচ্ছি যে কনভার্টারটি আমাদের users একটি পরিবর্তনযোগ্য তালিকা তৈরি করেছে যা বাতিলযোগ্য বস্তু ধারণ করে। যদিও তালিকাটি প্রকৃতপক্ষে নাল হতে পারে, আসুন বলি যে এটি নাল ব্যবহারকারীদের ধরে রাখতে পারে না। তো চলুন নিচের কাজগুলো করি:

  • সরান ? User? users টাইপ ঘোষণা মধ্যে
  • getUsers ফিরতে হবে List<User>?

অটো-কনভার্টারটি অপ্রয়োজনীয়ভাবে ব্যবহারকারীর ভেরিয়েবলের পরিবর্তনশীল ঘোষণা এবং init ব্লকে সংজ্ঞায়িত 2 লাইনে বিভক্ত করে। আসুন প্রতিটি পরিবর্তনশীল ঘোষণাকে এক লাইনে রাখি। আমাদের কোডটি কেমন হওয়া উচিত তা এখানে:

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 ব্লকে স্থাপন করা হয়। কার্যকারিতা একই।

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 ক্লাসের কোটলিন সংস্করণে, আমরা দেখতে পাচ্ছি যে ব্যবহারকারীদের সম্পত্তি ঘোষণায় শুরু করা হয়েছে।

private var users: MutableList<User>? = null

কোটলিনের static বৈশিষ্ট্য এবং পদ্ধতি

জাভাতে, আমরা ক্ষেত্র বা ফাংশনের জন্য static কীওয়ার্ড ব্যবহার করি যে তারা একটি শ্রেণীর অন্তর্গত কিন্তু ক্লাসের একটি উদাহরণ নয়। এই কারণেই আমরা আমাদের Repository ক্লাসে INSTANCE স্ট্যাটিক ফিল্ড তৈরি করেছি। এর জন্য কোটলিন সমতুল্য হল companion object ব্লক। এখানে আপনি স্ট্যাটিক ক্ষেত্র এবং স্ট্যাটিক ফাংশন ঘোষণা করবেন। রূপান্তরকারী এখানে INSTANCE ক্ষেত্র তৈরি এবং সরানো হয়েছে৷

সিঙ্গেলটন হ্যান্ডলিং

যেহেতু আমাদের Repository ক্লাসের শুধুমাত্র একটি উদাহরণ প্রয়োজন, আমরা জাভাতে সিঙ্গেলটন প্যাটার্ন ব্যবহার করেছি। 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 destructuring declaration নামে একটি সিনট্যাক্স ব্যবহার করে একটি বস্তুকে অনেকগুলি ভেরিয়েবলে ধ্বংস করার অনুমতি দেয়। আমরা একাধিক ভেরিয়েবল তৈরি করি এবং সেগুলি স্বাধীনভাবে ব্যবহার করতে পারি।

উদাহরণস্বরূপ, ডেটা ক্লাসগুলি ধ্বংসকে সমর্থন করে যাতে আমরা (firstName, lastName) এর for লুপে User অবজেক্টটিকে ধ্বংস করতে পারি। এটি আমাদের 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 ক্লাসকে কোটলিনে রূপান্তর করার সময়, স্বয়ংক্রিয় রূপান্তরকারী ব্যবহারকারীদের তালিকা বাতিলযোগ্য করে তোলে, কারণ এটি ঘোষণা করার সময় এটি একটি অবজেক্টে আরম্ভ করা হয়নি। users সমস্ত ব্যবহারের জন্য অবজেক্ট, নট-নাল অ্যাসারশন অপারেটর !! ব্যবহৃত হয়. এটি যেকোনো ভেরিয়েবলকে একটি নন-নাল টাইপে রূপান্তর করে এবং মানটি নাল হলে একটি ব্যতিক্রম ছুড়ে দেয়। ব্যবহার করে !! , আপনি রানটাইমে ব্যতিক্রমগুলি নিক্ষিপ্ত হওয়ার ঝুঁকি নিচ্ছেন৷

পরিবর্তে, এই পদ্ধতিগুলির মধ্যে একটি ব্যবহার করে অকার্যকরতা পরিচালনা করতে পছন্দ করুন:

  • একটি নাল চেক করা হচ্ছে ( if (users != null) {...} )
  • এলভিস অপারেটর ব্যবহার করে ?: (পরে কোডল্যাবে আচ্ছাদিত)
  • কিছু কোটলিন স্ট্যান্ডার্ড ফাংশন ব্যবহার করে (পরে কোডল্যাবে কভার করা হয়েছে)

আমাদের ক্ষেত্রে, আমরা জানি যে ব্যবহারকারীদের তালিকাটি বাতিল করার প্রয়োজন নেই, যেহেতু এটি অবজেক্টটি তৈরি হওয়ার পরে শুরু হয়, তাই আমরা যখন অবজেক্টটি ঘোষণা করি তখন আমরা সরাসরি তা ইনস্ট্যান্টিয়েট করতে পারি।

সংগ্রহের প্রকারের দৃষ্টান্ত তৈরি করার সময়, কোটলিন আপনার কোডকে আরও পঠনযোগ্য এবং নমনীয় করতে বেশ কয়েকটি সহায়ক ফাংশন প্রদান করে। এখানে আমরা 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 হতে পারে, তাই যখন আমরা বিন্যাসকৃত ব্যবহারকারীর নামের তালিকা তৈরি করি তখন আমাদের শূন্যতা পরিচালনা করতে হবে। যেহেতু কোন একটি নাম অনুপস্থিত থাকলে আমরা "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"

কোটলিনে if , when , for , এবং while হয় এক্সপ্রেশন-তারা একটি মান প্রদান করে। আপনার IDE এমনকি একটি সতর্কতাও দেখাচ্ছে যে অ্যাসাইনমেন্টটি যদি থেকে তুলে নেওয়া 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 ইউজারনেম গেটারকে আরও ঘনিষ্ঠভাবে দেখে নেওয়া যাক এবং দেখুন কিভাবে আমরা এটিকে আরও মূর্খতাপূর্ণ করতে পারি। এই মুহূর্তে কোড নিম্নলিখিত কাজ করে:

  • স্ট্রিংগুলির একটি নতুন তালিকা তৈরি করে
  • ব্যবহারকারীদের তালিকা মাধ্যমে পুনরাবৃত্তি
  • ব্যবহারকারীর প্রথম এবং শেষ নামের উপর ভিত্তি করে প্রতিটি ব্যবহারকারীর জন্য ফর্ম্যাট করা নাম তৈরি করে
  • সদ্য তৈরি তালিকা প্রদান করে
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 সংগ্রহ রূপান্তরের একটি বিস্তৃত তালিকা প্রদান করে যা জাভা সংগ্রহ API-এর ক্ষমতা প্রসারিত করে উন্নয়নকে দ্রুত এবং নিরাপদ করে। তাদের মধ্যে একটি map ফাংশন. এই ফাংশনটি মূল তালিকার প্রতিটি উপাদানে প্রদত্ত ট্রান্সফর্ম ফাংশন প্রয়োগের ফলাফল ধারণকারী একটি নতুন তালিকা প্রদান করে। সুতরাং, একটি নতুন তালিকা তৈরি করার পরিবর্তে এবং ব্যবহারকারীদের ম্যানুয়ালি তালিকার মাধ্যমে পুনরাবৃত্তি করার পরিবর্তে, আমরা map ফাংশনটি ব্যবহার করতে পারি এবং map বডির ভিতরে লুপে যে যুক্তিটি for তা সরাতে পারি। ডিফল্টরূপে, 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 প্রদান করে।

জাভাতে, আমরা গেটার এবং সেটার ফাংশনের মাধ্যমে আমাদের ক্লাসের বৈশিষ্ট্যগুলি প্রকাশ করব। Kotlin আমাদের একটি ক্লাসের বৈশিষ্ট্যগুলির মধ্যে একটি ভাল পার্থক্য করার অনুমতি দেয়, ক্ষেত্রগুলির সাথে প্রকাশ করা হয়, এবং কার্যকারিতাগুলি, কর্ম যা একটি ক্লাস করতে পারে, ফাংশনগুলির সাথে প্রকাশ করা হয়। আমাদের ক্ষেত্রে, Repository ক্লাসটি খুব সহজ এবং কোন কাজ করে না তাই এটির শুধুমাত্র ক্ষেত্র রয়েছে।

জাভা getFormattedUserNames() ফাংশনে যে লজিকটি ট্রিগার করা হয়েছিল সেটি এখন formattedUserNames ইউজারনেম কোটলিন প্রপার্টির গেটারকে কল করার সময় ট্রিগার করা হয়েছে।

যদিও আমাদের কাছে স্পষ্টভাবে formattedUserNames ইউজারনেম সম্পত্তির সাথে সম্পর্কিত একটি ক্ষেত্র নেই, কোটলিন আমাদের ক্ষেত্র নামে একটি স্বয়ংক্রিয় ব্যাকিং field প্রদান করে যা আমরা কাস্টম গেটার এবং সেটারের কাছ থেকে প্রয়োজনে অ্যাক্সেস করতে পারি।

কখনও কখনও, যাইহোক, আমরা কিছু অতিরিক্ত কার্যকারিতা চাই যা স্বয়ংক্রিয় ব্যাকিং ক্ষেত্র প্রদান করে না। চলুন নীচের একটি উদাহরণ মাধ্যমে যান.

আমাদের Repository ক্লাসের ভিতরে, আমাদের কাছে ব্যবহারকারীদের একটি পরিবর্তনযোগ্য তালিকা রয়েছে যা getUsers() ফাংশনে প্রকাশ করা হচ্ছে যা আমাদের জাভা কোড থেকে তৈরি হয়েছিল:

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 যে কোনো শ্রেণী, বস্তু, বা ইন্টারফেসের বাইরে ফাংশন এবং বৈশিষ্ট্য ঘোষণা করার ক্ষমতা প্রদান করে। উদাহরণস্বরূপ, একটি List একটি নতুন দৃষ্টান্ত তৈরি করতে আমরা যে mutableListOf() ফাংশনটি ব্যবহার করেছি তা স্ট্যান্ডার্ড লাইব্রেরি থেকে Collections.kt এ সরাসরি সংজ্ঞায়িত করা হয়েছে।

জাভাতে, যখনই আপনার কিছু ইউটিলিটি কার্যকারিতার প্রয়োজন হয়, আপনি সম্ভবত একটি Util ক্লাস তৈরি করবেন এবং সেই কার্যকারিতাটিকে একটি স্ট্যাটিক ফাংশন হিসাবে ঘোষণা করবেন। কোটলিনে আপনি ক্লাস না করেই উচ্চ-স্তরের ফাংশন ঘোষণা করতে পারেন। যাইহোক, কোটলিন এক্সটেনশন ফাংশন তৈরি করার ক্ষমতাও প্রদান করে। এগুলি এমন ফাংশন যা একটি নির্দিষ্ট ধরণের প্রসারিত করে তবে টাইপের বাইরে ঘোষণা করা হয়। যেমন, তাদের সেই ধরনের একটা সখ্যতা আছে।

এক্সটেনশন ফাংশন এবং বৈশিষ্ট্যগুলির দৃশ্যমানতা দৃশ্যমানতা পরিবর্তনকারী ব্যবহার করে সীমাবদ্ধ করা যেতে পারে। এগুলি শুধুমাত্র সেই ক্লাসগুলিতেই ব্যবহার সীমাবদ্ধ করে যেগুলির এক্সটেনশনগুলির প্রয়োজন, এবং নামস্থানকে দূষিত করে না৷

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

কোটলিন স্ট্যান্ডার্ড লাইব্রেরি বিভিন্ন জাভা API-এর কার্যকারিতা প্রসারিত করতে এক্সটেনশন ফাংশন ব্যবহার করে; Iterable এবং Collection অনেক কার্যকারিতা এক্সটেনশন ফাংশন হিসাবে প্রয়োগ করা হয়। উদাহরণস্বরূপ, পূর্ববর্তী ধাপে আমরা যে map ফাংশনটি ব্যবহার করেছি তা হল Iterable এ একটি এক্সটেনশন ফাংশন।

আমাদের Repository ক্লাস কোডে, আমরা _users তালিকায় বেশ কিছু ইউজার অবজেক্ট যোগ করছি। স্কোপ ফাংশনগুলির সাহায্যে এই কলগুলিকে আরও বাজে করা যেতে পারে।

শুধুমাত্র একটি নির্দিষ্ট অবজেক্টের প্রেক্ষাপটে কোড চালানোর জন্য, অবজেক্টের নামের উপর ভিত্তি করে অ্যাক্সেস করার প্রয়োজন ছাড়াই, Kotlin 5টি স্কোপ ফাংশন তৈরি করেছে: 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)
    }
 }

এই কোডল্যাবে, আমরা জাভা থেকে কোটলিন পর্যন্ত আপনার কোড রিফ্যাক্টরিং শুরু করার জন্য আপনার প্রয়োজনীয় মৌলিক বিষয়গুলি কভার করেছি। এই রিফ্যাক্টরিং আপনার ডেভেলপমেন্ট প্ল্যাটফর্ম থেকে স্বাধীন এবং আপনি যে কোডটি লিখছেন তা ইডিওম্যাটিক তা নিশ্চিত করতে সাহায্য করে।

ইডিওম্যাটিক কোটলিন লেখার কোড সংক্ষিপ্ত এবং মিষ্টি করে তোলে। Kotlin প্রদান করে সমস্ত বৈশিষ্ট্য সহ, আপনার কোডকে নিরাপদ, আরও সংক্ষিপ্ত এবং আরও পাঠযোগ্য করার অনেক উপায় রয়েছে৷ উদাহরণস্বরূপ, আমরা init ব্লক থেকে পরিত্রাণ পেয়ে সরাসরি ঘোষণায় ব্যবহারকারীদের সাথে _users তালিকাটি ইনস্ট্যান্টিয়েট করে আমাদের Repository শ্রেণীটি অপ্টিমাইজ করতে পারি:

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

আমরা শূন্যতা, এককটন, স্ট্রিংস এবং সংগ্রহগুলি পরিচালনা করা থেকে শুরু করে এক্সটেনশন ফাংশন, শীর্ষ-স্তরের ফাংশন, বৈশিষ্ট্য এবং স্কোপ ফাংশনগুলির মতো বিষয়গুলিকে কভার করেছি৷ আমরা দুটি জাভা ক্লাস থেকে দুটি কোটলিনে গিয়েছিলাম যা এখন দেখতে এইরকম:

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

এখানে জাভা কার্যকারিতাগুলির একটি TL;DR এবং কোটলিনে তাদের ম্যাপিং রয়েছে:

জাভা

কোটলিন

final বস্তু

val বস্তু

equals()

==

==

===

ক্লাস যে শুধু ডাটা ধারণ করে

data ক্লাস

কনস্ট্রাক্টরে সূচনা

init ব্লকে ইনিশিয়ালাইজেশন

static ক্ষেত্র এবং ফাংশন

একটি companion object ঘোষিত ক্ষেত্র এবং ফাংশন

সিঙ্গেলটন ক্লাস

object

কোটলিন সম্পর্কে আরও জানতে এবং কীভাবে এটি আপনার প্ল্যাটফর্মে ব্যবহার করবেন, এই সংস্থানগুলি দেখুন: