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

۱. خوش آمدید!

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

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

آنچه یاد خواهید گرفت

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

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

فرضیات

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

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

۲. راه‌اندازی

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

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

اگر از اندروید استودیو استفاده می‌کنید، یک پروژه جدید با الگوی No Activity ایجاد کنید. زبان پروژه را Kotlin انتخاب کنید. Minimum SDK می‌تواند هر مقداری داشته باشد، تاثیری بر نتیجه نخواهد داشت.

کد

ما یک شیء مدل User و یک کلاس Singleton از نوع Repository ایجاد خواهیم کرد که با اشیاء 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 تعریف نشده است. بنابراین اگر از اندروید استودیو استفاده می‌کنید 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;
    }
}

۳. اعلام nullability، val، var و کلاس‌های داده

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

به فایل User.java بروید و آن را به Kotlin تبدیل کنید: Menu bar -> Code -> Convert Java File to Kotlin File .

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

e6f96eace5dabe5f.png

شما باید کد کاتلین زیر را ببینید:

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

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

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

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

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

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

کلاس داده

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

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

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

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

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

در کاتلین، می‌توانیم مقادیر پیش‌فرض را به آرگومان‌ها در فراخوانی‌های تابع اختصاص دهیم. مقدار پیش‌فرض زمانی استفاده می‌شود که آرگومان حذف شود. در کاتلین، سازنده‌ها نیز توابع هستند، بنابراین می‌توانیم از آرگومان‌های پیش‌فرض برای تعیین اینکه مقدار پیش‌فرض 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")

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

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

Default values are an important and often used concept in Kotlin code. In our codelab we want to always specify the first and last name in a User object declaration, so we don't need default values.

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

قبل از ادامه‌ی کدنویسی، مطمئن شوید که کلاس 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 nullable است زیرا شیء در زمان تعریف نمونه‌سازی نشده است.
  • توابع در کاتلین مانند getUsers() با اصلاحگر fun تعریف می‌شوند.
  • متد getFormattedUserNames() اکنون یک ویژگی به نام formattedUserNames است.
  • تکرار روی لیست کاربران (که در ابتدا بخشی از getFormattedUserNames( ) بود) سینتکس متفاوتی نسبت به سینتکس جاوا دارد.
  • فیلد static اکنون بخشی از یک بلوک companion object است
  • یک بلوک init اضافه شد

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

  • علامت ? را از داخل تعریف نوع users User? حذف کنید.
  • علامت ? را از تابع ` User? getUsers() ` برای نوع بازگشتی آن حذف کنید تا List<User>? برگردانده شود.

بلوک اولیه

در کاتلین، سازنده اصلی نمی‌تواند شامل هیچ کدی باشد، بنابراین کد مقداردهی اولیه در بلوک‌های 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 ما، می‌بینیم که ویژگی users در اعلان مقداردهی اولیه شده است.

private var users: MutableList<User>? = null

ویژگی‌ها و متدهای static کاتلین

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

مدیریت سینگلتون‌ها

از آنجا که ما فقط به یک نمونه از کلاس Repository نیاز داریم، از الگوی singleton در جاوا استفاده کردیم. با کاتلین، می‌توانید با جایگزینی کلمه کلیدی 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

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

۶. مدیریت تهی‌پذیری

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

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

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

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

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

private var users: MutableList<User>? = null

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

private val users = mutableListOf<User>()

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

از آنجایی که متغیر 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 ما اکنون غیر تهی است و می‌توانیم تمام موارد غیرضروری عملگر !! را حذف کنیم. توجه داشته باشید که هنوز خطاهای کامپایل را در اندروید استودیو مشاهده خواهید کرد، اما برای حل آنها، مراحل بعدی codelabs را ادامه دهید.

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 از destructuring پشتیبانی می‌کنند، بنابراین می‌توانیم شیء User را در حلقه for به (firstName, lastName) destructure کنیم. این به ما امکان می‌دهد مستقیماً با مقادیر 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

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

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

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

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

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

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

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

۷. قالب‌های رشته‌ای

کاتلین با استفاده از قالب‌های رشته‌ای، کار با 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
    }

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

val formattedUserNames: List<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
        }

کاتلین فهرست گسترده‌ای از تبدیل‌های مجموعه را ارائه می‌دهد که با گسترش قابلیت‌های 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"
                }
            }
        }

۹. خواص و خواص پشتیبان

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

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

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

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

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

بیایید با یک مثال پیش برویم.

درون کلاس Repository ، یک لیست تغییرپذیر از کاربران داریم که در تابع getUsers() که از کد جاوای ما تولید شده است، نمایش داده می‌شود:

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

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

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

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

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

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

وقتی users از کد کاتلین فراخوانی می‌شود، از پیاده‌سازی List از کتابخانه استاندارد کاتلین استفاده می‌شود، که در آن لیست قابل تغییر نیست. اگر 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)
    }
}

۱۰. توابع و ویژگی‌های سطح بالا و افزونه

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

کاتلین قابلیت تعریف توابع و ویژگی‌ها را خارج از هر کلاس، شیء یا رابطی فراهم می‌کند. برای مثال، تابع mutableListOf() که ما برای ایجاد یک نمونه جدید از یک List استفاده کردیم، از قبل در 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 هستند.

از آنجا که نام فرمت‌شده یک ویژگی از کلاس User است و نه یک قابلیت از کلاس Repository ، بیایید از ویژگی extension استفاده کنیم. فایل 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 است.

۱۱. توابع محدوده: let، apply، with، run، also

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

برای اجرای کد فقط در چارچوب یک شیء خاص، بدون نیاز به دسترسی به شیء بر اساس نام آن، کاتلین 5 تابع scope ارائه می‌دهد: let ، apply ، with ، run و also . این توابع، خوانایی کد شما را آسان‌تر و مختصرتر می‌کنند. همه توابع scope یک receiver ( this ) دارند، ممکن است یک argument ( 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)
    }
 }

۱۲. جمع‌بندی

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

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

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

ما طیف وسیعی از موضوعات را پوشش دادیم، از مدیریت nullability، singletonها، Stringها و collectionها گرفته تا مباحثی مانند توابع توسعه‌یافته، توابع سطح بالا، ویژگی‌ها و توابع scope. ما از دو کلاس جاوا به دو کلاس کاتلین رسیدیم که اکنون به این شکل هستند:

کاربر.kt

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

مخزن.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 }
}

در اینجا خلاصه‌ای از قابلیت‌های جاوا و نگاشت آنها به کاتلین آمده است:

جاوا

کاتلین

شیء final

شیء val

equals()

==

==

===

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

کلاس data

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

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

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

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

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

object

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