(منسوخ شده) تبدیل به کاتلین

1. خوش آمدید!

در این کد لبه، یاد خواهید گرفت که چگونه کد خود را از جاوا به کاتلین تبدیل کنید. همچنین یاد خواهید گرفت که قراردادهای زبان کاتلین چیست و چگونه اطمینان حاصل کنید که کدی که می نویسید از آنها پیروی می کند.

این کد لبه برای هر توسعه‌دهنده‌ای که از جاوا استفاده می‌کند و قصد مهاجرت پروژه خود به Kotlin را دارد، مناسب است. ما با چند کلاس جاوا شروع می کنیم که با استفاده از IDE آنها را به Kotlin تبدیل خواهید کرد. سپس نگاهی به کد تبدیل شده بیندازیم و ببینیم چگونه می‌توانیم آن را با اصطلاحی‌تر کردن آن بهبود بخشیم و از دام‌های رایج جلوگیری کنیم.

چیزی که یاد خواهید گرفت

شما یاد خواهید گرفت که چگونه جاوا را به کاتلین تبدیل کنید. با انجام این کار، ویژگی ها و مفاهیم زبان کاتلین زیر را یاد خواهید گرفت:

  • مدیریت پوچ پذیری
  • اجرای تک قلوها
  • کلاس های داده
  • دست زدن به رشته ها
  • اپراتور الویس
  • در حال تخریب
  • خواص و ویژگی های پشتوانه
  • آرگومان های پیش فرض و پارامترهای نامگذاری شده
  • کار با مجموعه ها
  • توابع پسوند
  • توابع و پارامترهای سطح بالا
  • let ، apply ، with ، و کلمات کلیدی را run

مفروضات

شما باید از قبل با جاوا آشنا باشید.

آنچه شما نیاز دارید

2. راه اندازی

یک پروژه جدید ایجاد کنید

اگر از IntelliJ IDEA استفاده می کنید، یک پروژه جاوا جدید با Kotlin/JVM ایجاد کنید.

اگر از Android Studio استفاده می کنید، یک پروژه جدید با الگوی No Activity ایجاد کنید. Kotlin را به عنوان زبان پروژه انتخاب کنید. حداقل SDK می تواند هر ارزشی داشته باشد، بر نتیجه تأثیر نخواهد گذاشت.

کد

ما یک شی مدل User و یک کلاس Repository singleton ایجاد می کنیم که با اشیاء User کار می کند و لیستی از کاربران و نام های کاربری فرمت شده را نمایش می دهد.

یک فایل جدید به نام User.java در app/java/< yourpackagename > ایجاد کنید و کد زیر را قرار دهید:

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

}

متوجه خواهید شد که IDE شما به شما می گوید @Nullable تعریف نشده است. بنابراین اگر از Android Studio استفاده می کنید androidx.annotation.Nullable یا اگر از IntelliJ استفاده می کنید org.jetbrains.annotations.Nullable را وارد کنید.

یک فایل جدید به نام Repository.java ایجاد کنید و کد زیر را در آن قرار دهید:

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

public class Repository {

    private static Repository INSTANCE = null;

    private List<User> users = null;

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

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

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

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

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

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

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

3. اعلام پوچ پذیری، val، var و کلاس های داده

IDE ما می تواند کار بسیار خوبی در تبدیل خودکار کد جاوا به کد Kotlin انجام دهد، اما گاهی اوقات به کمک کمی نیاز دارد. اجازه دهید IDE ما یک پاس اولیه در تبدیل انجام دهد. سپس کد به دست آمده را مرور می کنیم تا بفهمیم چگونه و چرا به این شکل تبدیل شده است.

به فایل User.java بروید و آن را به Kotlin تبدیل کنید: نوار منو -> کد -> تبدیل فایل جاوا به فایل کاتلین .

اگر IDE شما بعد از تبدیل درخواست تصحیح کرد، Yes را فشار دهید.

e6f96eace5dabe5f.png

شما باید کد Kotlin زیر را ببینید:

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

توجه داشته باشید که User.java به User.kt تغییر نام داد. فایل های Kotlin دارای پسوند .kt هستند.

در کلاس Java User ما دو ویژگی داشتیم: firstName و lastName . هر کدام یک روش گیرنده و تنظیم کننده داشتند که باعث می شد مقدار آن قابل تغییر باشد. کلمه کلیدی کاتلین برای متغیرهای قابل تغییر var است، بنابراین مبدل برای هر یک از این ویژگی ها از var استفاده می کند. اگر خواص جاوا ما فقط دریافت کننده بود، فقط خواندنی بود و به عنوان متغیرهای val اعلام می شد. val شبیه کلمه کلیدی final در جاوا است.

یکی از تفاوت های کلیدی بین Kotlin و Java این است که Kotlin به صراحت مشخص می کند که آیا یک متغیر می تواند یک مقدار تهی را بپذیرد یا خیر. این کار را با الحاق یک ? به اعلامیه نوع

از آنجایی که ما firstName و lastName به عنوان nullable علامت گذاری کردیم، تبدیل خودکار به طور خودکار ویژگی ها را با String? . اگر اعضای جاوا خود را غیر تهی حاشیه نویسی کنید (با استفاده از org.jetbrains.annotations.NotNull یا androidx.annotation.NonNull )، مبدل این را تشخیص می دهد و فیلدها را در Kotlin نیز غیر تهی می کند.

تبدیل اولیه قبلا انجام شده است. اما می‌توانیم این را به شکلی اصطلاحی‌تر بنویسیم. بیایید ببینیم چگونه.

کلاس داده

کلاس User ما فقط داده ها را نگه می دارد. کاتلین یک کلمه کلیدی برای کلاس هایی با این نقش دارد: data . با علامت گذاری این کلاس به عنوان کلاس data ، کامپایلر به طور خودکار دریافت کننده ها و تنظیم کننده ها را برای ما ایجاد می کند. همچنین توابع equals() ، hashCode() و toString() را مشتق می کند.

بیایید کلمه کلیدی data را به کلاس User خود اضافه کنیم:

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

کاتلین مانند جاوا می تواند یک سازنده اولیه و یک یا چند سازنده ثانویه داشته باشد. یکی در مثال بالا سازنده اصلی کلاس User است. اگر در حال تبدیل یک کلاس جاوا هستید که چندین سازنده دارد، مبدل به طور خودکار چندین سازنده را در Kotlin نیز ایجاد می کند. آنها با استفاده از کلمه کلیدی 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

4. آرگومان های پیش فرض، آرگومان های نامگذاری شده

در Kotlin، می‌توانیم مقادیر پیش‌فرض را به آرگومان‌های فراخوانی تابع اختصاص دهیم. مقدار پیش فرض زمانی استفاده می شود که آرگومان حذف شود. در Kotlin، سازنده‌ها نیز تابع هستند، بنابراین می‌توانیم از آرگومان‌های پیش‌فرض برای تعیین اینکه مقدار پیش‌فرض lastName null است استفاده کنیم. برای انجام این کار، فقط null به lastName اختصاص می دهیم.

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

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

Kotlin به شما اجازه می دهد تا زمانی که توابع شما فراخوانی می شوند، آرگومان های خود را برچسب گذاری کنید:

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

به عنوان یک مورد استفاده متفاوت، بیایید بگوییم که firstName مقدار پیش‌فرض آن null است و lastName ندارد. در این حالت، چون پارامتر پیش‌فرض قبل از پارامتری بدون مقدار پیش‌فرض قرار می‌گیرد، باید تابع را با آرگومان‌های نام‌گذاری شده فراخوانی کنید:

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

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

مقادیر پیش فرض یک مفهوم مهم و اغلب مورد استفاده در کد Kotlin هستند. در Codelab ما می خواهیم همیشه نام و نام خانوادگی را در یک اعلان شی User مشخص کنیم، بنابراین به مقادیر پیش فرض نیاز نداریم.

5. مقداردهی اولیه شیء، شیء همراه و تک آهنگها

قبل از ادامه Codelab، مطمئن شوید که کلاس User شما یک کلاس data است. حالا بیایید کلاس Repository را به Kotlin تبدیل کنیم. نتیجه تبدیل خودکار باید به شکل زیر باشد:

import java.util.*

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

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

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

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

بیایید ببینیم مبدل خودکار چه کاری انجام داد:

  • لیست users باطل است زیرا شی در زمان اعلان نمونه سازی نشده است
  • توابعی در Kotlin مانند getUsers() با تعدیل کننده fun اعلام می شوند
  • متد getFormattedUserNames() اکنون یک ویژگی به نام formattedUserNames است
  • تکرار بر روی لیست کاربران (که در ابتدا بخشی از getFormattedUserNames( ) بود) دارای نحو متفاوتی نسبت به جاوا است.
  • فیلد static اکنون بخشی از یک بلوک companion object است
  • یک بلوک init اضافه شد

قبل از اینکه جلوتر برویم، اجازه دهید کد را کمی پاک کنیم. اگر به سازنده نگاه کنیم، متوجه می‌شویم که مبدل باعث شده است که users ما یک لیست قابل تغییر را فهرست کنند که اشیاء تهی را در خود نگه می‌دارد. در حالی که لیست واقعاً می تواند تهی باشد، بیایید فرض کنیم که نمی تواند کاربران تهی را نگه دارد. پس بیایید کارهای زیر را انجام دهیم:

  • حذف کنید ? در User? در اعلان تایپ users
  • حذف کنید ? در User? برای نوع برگشتی getUsers() پس List<User>?

بلوک راه اندازی

در 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 ویژگی های اولیه را کنترل می کنند. در اظهارنامه ملک نیز می توان این کار را انجام داد. به عنوان مثال، در نسخه Kotlin از کلاس Repository ما، می بینیم که ویژگی users در اعلان مقداردهی اولیه شده است.

private var users: MutableList<User>? = null

خواص و روش های static کاتلین

در جاوا، از کلمه کلیدی static برای فیلدها یا توابع استفاده می کنیم تا بگوییم که آنها متعلق به یک کلاس هستند اما به نمونه ای از کلاس تعلق ندارند. به همین دلیل است که ما فیلد استاتیک INSTANCE را در کلاس Repository خود ایجاد کردیم. معادل Kotlin برای این بلوک companion object است. در اینجا شما همچنین می توانید فیلدهای استاتیک و توابع استاتیک را اعلام کنید. مبدل بلوک شیء همراه را ایجاد کرد و فیلد INSTANCE را به اینجا منتقل کرد.

رسیدگی به تک قلوها

از آنجایی که ما فقط به یک نمونه از کلاس Repository نیاز داریم، از الگوی singleton در جاوا استفاده کردیم. با Kotlin، می‌توانید این الگو را در سطح کامپایلر با جایگزین کردن کلمه کلیدی class با object اجرا کنید.

سازنده خصوصی را حذف کرده و تعریف کلاس را با object Repository جایگزین کنید. شیء همراه را نیز حذف کنید.

object Repository {

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

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

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

هنگام استفاده از کلاس object ، فقط توابع و خصوصیات را مستقیماً روی شیء فراخوانی می کنیم، مانند این:

val formattedUserNames = Repository.formattedUserNames

توجه داشته باشید که اگر یک ویژگی دارای یک اصلاح کننده دید نباشد، به طور پیش فرض عمومی است، مانند ویژگی formattedUserNames در شی Repository .

6. رسیدگی به پوچ بودن

هنگام تبدیل کلاس Repository به Kotlin، مبدل خودکار فهرست کاربران را باطل می‌کند، زیرا در زمان اعلام به یک شی مقداردهی اولیه نمی‌شد. در نتیجه، برای تمام موارد استفاده users ، عملگر ادعای تهی نیست !! نیاز به استفاده دارد. (شما users!! و user!! در سراسر کد تبدیل شده خواهید دید.) !! اپراتور هر متغیری را به یک نوع غیر تهی تبدیل می کند، بنابراین می توانید به ویژگی ها یا فراخوانی توابع روی آن دسترسی داشته باشید. با این حال، اگر مقدار متغیر واقعاً صفر باشد، یک استثنا ایجاد می شود. با استفاده از !! ، شما در معرض خطر قرار گرفتن استثناها در زمان اجرا هستید.

درعوض، با استفاده از یکی از این روش‌ها، مدیریت پوچ‌پذیری را ترجیح دهید:

  • انجام بررسی تهی ( if (users != null) {...} )
  • استفاده از عملگر elvis ?: (بعداً در بخش کدها پوشش داده شد)
  • استفاده از برخی از توابع استاندارد Kotlin (که بعداً در نرم افزار کد پوشش داده شد)

در مورد ما، می دانیم که لیست کاربران نیازی به پوچ شدن ندارد، زیرا درست پس از ساخت شی (در بلوک init ) مقداردهی اولیه می شود. بنابراین ما می‌توانیم مستقیماً شیء users را هنگام اعلام آن نمونه‌سازی کنیم.

هنگام ایجاد نمونه‌هایی از انواع مجموعه، کاتلین چندین توابع کمکی را ارائه می‌کند تا کد شما را خواناتر و انعطاف‌پذیرتر کند. در اینجا ما از یک MutableList برای users استفاده می کنیم:

private var users: MutableList<User>? = null

برای سادگی، می‌توانیم از تابع mutableListOf() استفاده کنیم و نوع عنصر لیست را ارائه کنیم. mutableListOf<User>() یک لیست خالی ایجاد می کند که می تواند اشیاء User را در خود نگه دارد. از آنجایی که اکنون می توان نوع داده متغیر را توسط کامپایلر استنباط کرد، اعلان نوع صریح ویژگی users را حذف کنید.

private val users = mutableListOf<User>()

ما همچنین var به val تغییر دادیم زیرا کاربران دارای یک مرجع فقط خواندنی به لیست کاربران هستند. توجه داشته باشید که مرجع فقط خواندنی است، بنابراین هرگز نمی تواند به یک لیست جدید اشاره کند، اما خود لیست همچنان قابل تغییر است (شما می توانید عناصر را اضافه یا حذف کنید).

از آنجایی که متغیر users قبلاً مقداردهی اولیه شده است، این مقداردهی اولیه را از بلوک init حذف کنید:

users = ArrayList<Any?>()

سپس بلوک init باید به شکل زیر باشد:

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

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

با این تغییرات، ویژگی users ما اکنون غیر پوچ است و می توانیم تمام موارد غیر ضروری را حذف کنیم !! وقوع اپراتور توجه داشته باشید که همچنان خطاهای کامپایل را در اندروید استودیو مشاهده خواهید کرد، اما برای رفع آن‌ها به چند مرحله بعدی کد لبه‌ها ادامه دهید.

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

همچنین، برای مقدار userNames ، اگر نوع ArrayList را به‌عنوان نگهدارنده Strings مشخص کنید، می‌توانید نوع صریح را در اعلان حذف کنید زیرا استنباط می‌شود.

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

در حال تخریب

کاتلین امکان تخریب یک شی را به تعدادی متغیر با استفاده از نحوی به نام destructuring declaration می دهد. ما چندین متغیر ایجاد می کنیم و می توانیم به طور مستقل از آنها استفاده کنیم.

برای مثال، کلاس‌های data از ساختارشکنی پشتیبانی می‌کنند، بنابراین می‌توانیم شی User را در حلقه for در (firstName, lastName) تخریب کنیم. این به ما امکان می دهد مستقیماً با مقادیر firstName و lastName کار کنیم. حلقه for را مطابق شکل زیر به روز کنید. همه نمونه های user.firstName را با firstName و user.lastName با lastName جایگزین کنید.

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

اگر بیان

نام‌های موجود در فهرست نام‌های کاربری هنوز کاملاً در قالب مورد نظر ما نیستند. از آنجایی که هم lastName و firstName می‌توانند null باشند، باید وقتی لیستی از نام‌های کاربری قالب‌بندی شده را می‌سازیم، قابلیت پوچ‌پذیری را مدیریت کنیم. ما می خواهیم "Unknown" را در صورتی که هر یک از نام ها گم شده باشد نمایش دهیم. از آنجایی که متغیر name پس از یک بار تنظیم تغییر نمی کند، می توانیم به جای var از val استفاده کنیم. ابتدا این تغییر را انجام دهید.

val name: String

به کدی که متغیر نام را تنظیم می کند نگاه کنید. ممکن است برای شما جدید به نظر برسد که یک متغیر را با یک بلوک if / else تنظیم کنید. این مجاز است زیرا در Kotlin if and when are expressions-آنها یک مقدار را برمی گرداند. آخرین خط دستور if به name اختصاص داده می شود. تنها هدف این بلوک مقداردهی name است.

اساساً، این منطق ارائه شده در اینجا این است که اگر lastName پوچ باشد، name یا روی firstName یا "Unknown" تنظیم می شود.

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

اپراتور الویس

این کد را می توان با استفاده از عملگر elvis ?: به صورت اصطلاحی تر نوشت. عملگر elvis اگر تهی نباشد، عبارت سمت چپ را برمی‌گرداند، یا اگر سمت چپ تهی باشد، عبارت سمت راست را برمی‌گرداند.

بنابراین در کد زیر اگر null نباشد firstName برگردانده می شود. اگر firstName null باشد، عبارت در سمت راست مقدار "Unknown" را برمی گرداند:

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

7. قالب های رشته ای

Kotlin کار با String را با قالب های String آسان می کند. قالب های رشته ای به شما این امکان را می دهند که با استفاده از نماد $ قبل از متغیر، به متغیرهای داخل اعلان رشته ها ارجاع دهید. همچنین می توانید یک عبارت را در یک اعلان رشته قرار دهید، با قرار دادن عبارت در { } و استفاده از نماد $ قبل از آن. مثال: ${user.firstName} .

کد شما در حال حاضر از الحاق رشته برای ترکیب firstName و lastName در نام کاربری استفاده می کند.

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

در عوض، الحاق رشته را با:

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

استفاده از قالب های رشته ای می تواند کد شما را ساده کند.

اگر راه اصطلاحی تری برای نوشتن کدتان وجود داشته باشد، IDE شما هشدارهایی را به شما نشان می دهد. متوجه یک زیرخط موزون در کد می‌شوید و هنگامی که ماوس را روی آن می‌برید، پیشنهادی برای نحوه تغییر شکل کد خود خواهید دید.

در حال حاضر، باید اخطاری را ببینید مبنی بر اینکه اعلان name را می توان با انتساب ملحق کرد. بیایید این را اعمال کنیم. از آنجا که نوع متغیر name قابل استنباط است، می توانیم اعلان نوع String صریح را حذف کنیم. اکنون formattedUserNames ما به شکل زیر است:

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

ما می توانیم یک ترفند اضافی انجام دهیم. منطق UI ما در صورتی که نام و نام خانوادگی موجود نباشد، "Unknown" را نشان می دهد، بنابراین از اشیاء پوچ پشتیبانی نمی کنیم. بنابراین، برای نوع داده formattedUserNames List<String?> با List<String> جایگزین کنید.

val formattedUserNames: List<String>

8. عملیات روی مجموعه ها

بیایید نگاهی دقیق‌تر به formattedUserNames بگیریم و ببینیم چگونه می‌توانیم آن را اصطلاحی‌تر کنیم. در حال حاضر کد به صورت زیر عمل می کند:

  • یک لیست جدید از رشته ها ایجاد می کند
  • از طریق لیست کاربران تکرار می شود
  • نام قالب بندی شده برای هر کاربر را بر اساس نام و نام خانوادگی کاربر می سازد
  • لیست تازه ایجاد شده را برمی گرداند
    val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users.size)
            for ((firstName, lastName) in users) {
                val name = if (lastName != null) {
                    if (firstName != null) {
                        "$firstName $lastName"
                    } else {
                        lastName
                    }
                } else {
                    firstName ?: "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

Kotlin فهرست گسترده ای از تغییرات مجموعه ارائه می دهد که با گسترش قابلیت های Java Collections API توسعه را سریع تر و ایمن تر می کند. یکی از آنها تابع map است. این تابع یک لیست جدید حاوی نتایج اعمال تابع تبدیل داده شده به هر عنصر در لیست اصلی را برمی گرداند. بنابراین، به جای ایجاد یک لیست جدید و تکرار در لیست کاربران به صورت دستی، می‌توانیم از تابع map استفاده کنیم و منطقی را که در حلقه for داشتیم به داخل بدنه map منتقل کنیم. به‌طور پیش‌فرض، نام آیتم فهرست فعلی مورد استفاده در map it است، اما برای خوانایی می‌توانید it با نام متغیر خود جایگزین کنید. در مورد ما، اجازه دهید نام آن را user بگذاریم:

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

توجه داشته باشید که اگر user.lastName null باشد از عملگر Elvis برای برگرداندن "Unknown" استفاده می کنیم، زیرا user.lastName از نوع String? و یک String برای name مورد نیاز است.

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

برای ساده‌تر کردن این موضوع، می‌توانیم متغیر name را به طور کامل حذف کنیم:

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

9. خواص و خواص پشتوانه

دیدیم که مبدل خودکار تابع getFormattedUserNames() را با خاصیتی به نام formattedUserNames جایگزین کرد که یک گیرنده سفارشی دارد. در زیر هود، کاتلین همچنان یک متد getFormattedUserNames() تولید می کند که یک List برمی گرداند.

در جاوا، ویژگی های کلاس خود را از طریق توابع گیرنده و تنظیم کننده نمایش می دهیم. کاتلین به ما این امکان را می‌دهد که تمایز بهتری بین ویژگی‌های یک کلاس، که با فیلدها بیان می‌شوند، و عملکردها، اقداماتی که یک کلاس می‌تواند انجام دهد و با توابع بیان می‌شود، داشته باشیم. در مورد ما، کلاس Repository بسیار ساده است و هیچ عملی را انجام نمی دهد، بنابراین فقط دارای فیلدها است.

منطقی که در تابع getFormattedUserNames() جاوا راه اندازی شده بود، اکنون هنگام فراخوانی گیرنده ویژگی formattedUserNames Kotlin فعال می شود.

در حالی که ما صراحتاً فیلدی مطابق با ویژگی formattedUserNames نداریم، Kotlin یک فیلد پشتیبان خودکار به نام field در اختیار ما قرار می دهد که در صورت نیاز می توانیم از دریافت کننده ها و تنظیم کننده های سفارشی به آن دسترسی داشته باشیم.

با این حال، گاهی اوقات ما می خواهیم برخی از عملکردهای اضافی که زمینه پشتیبان خودکار فراهم نمی کند.

بیایید یک مثال را مرور کنیم.

در داخل کلاس Repository ، ما یک لیست قابل تغییر از کاربران داریم که در تابع getUsers() که از کد جاوا ما تولید شده است، در معرض نمایش قرار می گیرد:

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

چون نمی‌خواستیم فراخوان‌کنندگان کلاس Repository لیست کاربران را تغییر دهند، تابع getUsers() را ایجاد کردیم که یک List<User> فقط خواندنی را برمی‌گرداند. با Kotlin، ما ترجیح می دهیم از ویژگی ها به جای توابع برای چنین مواردی استفاده کنیم. به طور دقیق تر، ما یک List<User> فقط خواندنی را نشان می دهیم که توسط یک mutableListOf<User> پشتیبانی می شود.

ابتدا نام users به _users تغییر می دهیم. نام متغیر را برجسته کنید، روی Refactor کلیک کنید > تغییر نام متغیر را تغییر دهید . سپس یک ویژگی عمومی فقط خواندنی اضافه کنید که لیستی از کاربران را برمی گرداند. بیایید آن را users بنامیم:

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

در این مرحله می توانید متد getUsers() را حذف کنید.

با تغییر فوق، ویژگی private _users به ​​ویژگی پشتیبان برای ویژگی عمومی users تبدیل می شود. خارج از کلاس Repository ، لیست _users قابل تغییر نیست، زیرا مصرف کنندگان کلاس فقط از طریق users می توانند به لیست دسترسی داشته باشند.

هنگامی که users از کد Kotlin فراخوانی می شوند، پیاده سازی List از کتابخانه استاندارد Kotlin استفاده می شود، جایی که لیست قابل تغییر نیست. اگر users از جاوا فراخوانی شوند، پیاده سازی java.util.List استفاده می شود، جایی که لیست قابل تغییر است و عملیات هایی مانند add() و remove() در دسترس هستند.

کد کامل:

object Repository {

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

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

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

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

10. توابع و ویژگی های سطح بالا و پسوند

در حال حاضر کلاس Repository می داند که چگونه نام کاربری فرمت شده را برای یک شی User محاسبه کند. اما اگر بخواهیم از همان منطق قالب‌بندی مجدد در کلاس‌های دیگر استفاده کنیم، باید آن را کپی و پیست کنیم یا به کلاس User منتقل کنیم.

Kotlin توانایی اعلام توابع و خصوصیات خارج از هر کلاس، شی یا رابط را فراهم می کند. به عنوان مثال، تابع mutableListOf() که ما برای ایجاد یک نمونه جدید از یک List استفاده کردیم، قبلاً در Collections.kt از کتابخانه استاندارد Kotlin تعریف شده است.

در جاوا، هر زمان که به برخی از قابلیت های کاربردی نیاز داشتید، به احتمال زیاد یک کلاس Util ایجاد می کنید و آن عملکرد را به عنوان یک تابع ثابت اعلام می کنید. در Kotlin می توانید توابع سطح بالا را بدون داشتن کلاس اعلام کنید. با این حال، Kotlin همچنین توانایی ایجاد توابع افزونه را فراهم می کند. اینها توابعی هستند که نوع خاصی را گسترش می دهند اما خارج از نوع اعلام می شوند.

مشاهده توابع و خصوصیات افزونه را می توان با استفاده از اصلاح کننده های دید محدود کرد. اینها استفاده را فقط به کلاس هایی محدود می کنند که به پسوند نیاز دارند و فضای نام را آلوده نمی کنند.

برای کلاس User ، می‌توانیم یک تابع افزونه اضافه کنیم که نام قالب‌بندی شده را محاسبه می‌کند، یا می‌توانیم نام قالب‌بندی شده را در یک ویژگی پسوند نگه داریم. می توان آن را خارج از کلاس Repository ، در همان فایل اضافه کرد:

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

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

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

سپس می‌توانیم از توابع و ویژگی‌های افزونه استفاده کنیم که گویی بخشی از کلاس User هستند.

از آنجایی که نام فرمت شده یک ویژگی از کلاس User است و یک عملکرد کلاس Repository نیست، بیایید از ویژگی پسوند استفاده کنیم. فایل Repository ما اکنون به شکل زیر است:

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

object Repository {

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

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

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

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

کتابخانه استاندارد Kotlin از توابع افزونه برای گسترش عملکرد چندین API جاوا استفاده می کند. بسیاری از عملکردهای Iterable و Collection به عنوان توابع افزونه پیاده سازی می شوند. برای مثال، تابع map که در مرحله قبل استفاده کردیم، یک تابع پسوندی در Iterable است.

11. توابع دامنه: let, application, with, run, also

در کد کلاس Repository خود، چندین شیء User را به لیست _users اضافه می کنیم. این فراخوانی ها را می توان با کمک توابع دامنه Kotlin اصطلاحی تر کرد.

برای اجرای کد فقط در زمینه یک شی خاص، بدون نیاز به دسترسی به شی بر اساس نام آن، کاتلین 5 تابع محدوده را ارائه می دهد: let , apply , with , run و also . این توابع خواندن کد شما را آسان‌تر و مختصرتر می‌کنند. همه توابع scope دارای یک گیرنده ( this )، ممکن است یک آرگومان ( it ) داشته باشند و ممکن است یک مقدار برگردانند.

در اینجا یک برگه تقلب مفید وجود دارد که به شما کمک می کند زمان استفاده از هر تابع را به خاطر بسپارید:

6b9283d411fb6e7b.png

از آنجایی که ما در حال پیکربندی شی _users خود در Repository خود هستیم، می‌توانیم با استفاده از تابع apply ، کد را اصطلاحی‌تر کنیم:

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

12. جمع کنید

در این کد لبه، ما اصول اولیه ای را که برای شروع تبدیل کد خود از جاوا به کاتلین نیاز دارید، توضیح دادیم. این تبدیل مستقل از پلتفرم توسعه شما است و به اطمینان از اینکه کدی که می نویسید اصطلاحی Kotlin است کمک می کند.

Kotlin اصطلاحی نوشتن کد را کوتاه و شیرین می کند. با تمام ویژگی‌هایی که کاتلین ارائه می‌کند، راه‌های زیادی برای ایمن‌تر، مختصرتر و خواناتر کردن کد شما وجود دارد. برای مثال، ما حتی می‌توانیم کلاس Repository خود را با نمونه‌سازی فهرست _users با کاربرانی که مستقیماً در اعلان هستند، بهینه کنیم و از شر بلوک init خلاص شویم:

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

ما مجموعه وسیعی از موضوعات را پوشش دادیم، از مدیریت پوچ‌پذیری، تک‌تن‌ها، رشته‌ها و مجموعه‌ها گرفته تا موضوعاتی مانند توابع افزودنی، توابع سطح بالا، ویژگی‌ها و توابع دامنه. ما از دو کلاس جاوا به دو کلاس کاتلین رفتیم که اکنون شبیه به این هستند:

User.kt

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

Repository.kt

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

object Repository {

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

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

در اینجا یک TL;DR از عملکردهای جاوا و نگاشت آنها به Kotlin آمده است:

جاوا

کاتلین

شی final

شی val

equals()

==

==

===

کلاسی که فقط داده ها را نگه می دارد

کلاس data

مقداردهی اولیه در سازنده

مقداردهی اولیه در بلوک init

فیلدها و توابع static

فیلدها و توابع اعلام شده در یک companion object

کلاس سینگلتون

object

برای کسب اطلاعات بیشتر در مورد Kotlin و نحوه استفاده از آن در پلتفرم خود، این منابع را بررسی کنید: