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 را فشار دهید.
شما باید کد 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
) داشته باشند و ممکن است یک مقدار برگردانند.
در اینجا یک برگه تقلب مفید وجود دارد که به شما کمک می کند زمان استفاده از هر تابع را به خاطر بسپارید:
از آنجایی که ما در حال پیکربندی شی _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 آمده است:
جاوا | کاتلین |
شی | شی |
| |
| |
کلاسی که فقط داده ها را نگه می دارد | کلاس |
مقداردهی اولیه در سازنده | مقداردهی اولیه در بلوک |
فیلدها و توابع | فیلدها و توابع اعلام شده در یک |
کلاس سینگلتون | |
برای کسب اطلاعات بیشتر در مورد Kotlin و نحوه استفاده از آن در پلتفرم خود، این منابع را بررسی کنید:
- کاتلین کوانس
- آموزش های کاتلین
- Android Kotlin Fundamentals
- Kotlin Bootcamp برای برنامه نویسان
- Kotlin برای توسعه دهندگان جاوا - دوره رایگان در حالت حسابرسی