درباره این codelab
1. خوش آمدی!
در این کد لبه، یاد خواهید گرفت که چگونه کد خود را از جاوا به کاتلین تبدیل کنید. همچنین یاد خواهید گرفت که قراردادهای زبان کاتلین چیست و چگونه اطمینان حاصل کنید که کدی که می نویسید از آنها پیروی می کند.
این کد لبه برای هر توسعهدهندهای که از جاوا استفاده میکند و قصد مهاجرت پروژه خود به Kotlin را دارد، مناسب است. ما با چند کلاس جاوا شروع می کنیم که با استفاده از IDE آنها را به Kotlin تبدیل خواهید کرد. سپس نگاهی به کد تبدیل شده می اندازیم و می بینیم که چگونه می توانیم آن را با اصطلاحی تر کردن آن بهبود بخشیم و از دام های رایج جلوگیری کنیم.
چیزی که یاد خواهید گرفت
شما یاد خواهید گرفت که چگونه جاوا را به کاتلین تبدیل کنید. با انجام این کار، ویژگی ها و مفاهیم زبان کاتلین زیر را یاد خواهید گرفت:
- مدیریت پوچ پذیری
- اجرای تک قلوها
- کلاس های داده
- دست زدن به رشته ها
- اپراتور الویس
- در حال تخریب
- خواص و ویژگی های پشتوانه
- آرگومان های پیش فرض و پارامترهای نامگذاری شده
- کار با مجموعه ها
- توابع پسوند
- توابع و پارامترهای سطح بالا
-
let
،apply
کنید،with
کلمات کلیدی وrun
کنید
مفروضات
شما باید قبلا با جاوا آشنایی داشته باشید.
آنچه شما نیاز دارید
2. در حال راه اندازی
یک پروژه جدید ایجاد کنید
اگر از IntelliJ IDEA استفاده می کنید، یک پروژه جاوا جدید با Kotlin/JVM ایجاد کنید.
اگر از اندروید استودیو استفاده میکنید، پروژه جدیدی بدون فعالیت ایجاد کنید.
کد
ما یک شی مدل 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;
}
}
بسته به نوع androidx.annotation.Nullable
، اگر از پروژه Android استفاده میکنید، androidx.annotation.Nullable یا org.jetbrains.annotations.Nullable
کنید.
یک فایل جدید به نام Repository.java
ایجاد کنید و کد زیر را در آن قرار دهید:
import java.util.ArrayList;
import java.util.List;
public class Repository {
private static Repository INSTANCE = null;
private List<User> users = null;
public static Repository getInstance() {
if (INSTANCE == null) {
synchronized (Repository.class) {
if (INSTANCE == null) {
INSTANCE = new Repository();
}
}
}
return INSTANCE;
}
// keeping the constructor private to enforce the usage of getInstance
private Repository() {
User user1 = new User("Jane", "");
User user2 = new User("John", null);
User user3 = new User("Anne", "Doe");
users = new ArrayList();
users.add(user1);
users.add(user2);
users.add(user3);
}
public List<User> getUsers() {
return users;
}
public List<String> getFormattedUserNames() {
List<String> userNames = new ArrayList<>(users.size());
for (User user : users) {
String name;
if (user.getLastName() != null) {
if (user.getFirstName() != null) {
name = user.getFirstName() + " " + user.getLastName();
} else {
name = user.getLastName();
}
} else if (user.getFirstName() != null) {
name = user.getFirstName();
} else {
name = "Unknown";
}
userNames.add(name);
}
return userNames;
}
}
3. اعلام پوچ پذیری، val، var و کلاس های داده
IDE ما میتواند کار بسیار خوبی در تبدیل خودکار کد جاوا به کد Kotlin انجام دهد، اما گاهی اوقات به کمک کمی نیاز دارد. ابتدا این کار را انجام میدهیم و سپس کد بازسازیشده را مرور میکنیم تا بفهمیم که چگونه و چرا به این روش بازسازی شده است.
به فایل 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
ما فقط داده ها را نگه می دارد. Kotlin یک کلمه کلیدی برای کلاس هایی با این نقش دارد: 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("John", "Doe")
هنگام فراخوانی توابع می توان پارامترهای تابع را نام برد:
val john = User(firstName = "John", lastName = "Doe")
به عنوان یک مورد استفاده متفاوت، بیایید بگوییم که firstName
مقدار پیشفرض آن null
است و lastName
ندارد. در این حالت، از آنجایی که پارامتر پیشفرض قبل از پارامتری بدون مقدار پیشفرض قرار میگیرد، باید تابع را با آرگومانهای نامگذاری شده فراخوانی کنید:
data class User(var firstName: String? = null, var lastName: String?)
// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")
5. مقداردهی اولیه شیء، شیء همراه و تک آهنگ
قبل از رفتن به جلو، مطمئن شوید که کلاس 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)
}
}
بیایید ببینیم مبدل خودکار چه کاری انجام داد:
- یک بلوک
init
اضافه شد (Repository.kt#L50) - فیلد
static
اکنون بخشی از یک بلوکcompanion object
(Repository.kt#L33) - لیست
users
باطل شدنی است زیرا شی در زمان اعلان نمونه سازی نشده است (Repository.kt#L7) -
getFormattedUserNames()
اکنون یک ویژگی به نامformattedUserNames
(Repository.kt#L11) است. - تکرار بر روی لیست کاربران (که در ابتدا بخشی از
getFormattedUserNames(
) بود) دارای نحو متفاوتی نسبت به جاوا است (Repository.kt#L15)
قبل از اینکه جلوتر برویم، اجازه دهید کد را کمی پاک کنیم. میتوانیم ببینیم که مبدل، users
ما را فهرستی قابل تغییر ایجاد کرده است که اشیاء قابل تهی را در خود نگه میدارد. در حالی که لیست واقعاً می تواند تهی باشد، بیایید بگوییم که نمی تواند کاربران تهی را نگه دارد. پس بیایید کارهای زیر را انجام دهیم:
- حذف کنید
?
درUser?
در اعلان تایپusers
-
getUsers
بایدList<User>?
مبدل خودکار همچنین اعلان های متغیر متغیرهای کاربران و آنهایی را که در بلوک init تعریف شده اند، به طور غیر ضروری به 2 خط تقسیم می کند. بیایید هر اعلان متغیر را در یک خط قرار دهیم. در اینجا کد ما باید شبیه به آن باشد:
class Repository private constructor() {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
بلوک راه اندازی
در 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 users = Repository.users
در حال تخریب
کاتلین امکان تخریب یک شی را به تعدادی متغیر با استفاده از نحوی به نام destructuring declaration می دهد. ما چندین متغیر ایجاد می کنیم و می توانیم به طور مستقل از آنها استفاده کنیم.
برای مثال، کلاسهای داده از ساختارشکنی پشتیبانی میکنند، بنابراین میتوانیم شی User
را در حلقه for
در (firstName, lastName)
تخریب کنیم. این به ما امکان می دهد مستقیماً با مقادیر firstName
و lastName
کار کنیم. بیایید حلقه for
را به صورت زیر به روز کنیم:
for ((firstName, lastName) in users!!) {
val name: String?
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
userNames.add(name)
}
6. مدیریت پوچ پذیری
هنگام تبدیل کلاس Repository
به Kotlin، مبدل خودکار لیست کاربران را باطل میکند، زیرا در زمان اعلام به یک شی مقداردهی اولیه نمیشد. برای همه موارد استفاده users
، عملگر ادعای تهی نیست !!
استفاده می شود. هر متغیری را به یک نوع غیر تهی تبدیل میکند و در صورت تهی بودن مقدار، یک استثنا ایجاد میکند. با استفاده از !!
، شما در معرض خطر قرار گرفتن استثناها در زمان اجرا هستید.
درعوض، با استفاده از یکی از این روشها، مدیریت پوچپذیری را ترجیح دهید:
- انجام بررسی تهی (
if (users != null) {...}
) - استفاده از عملگر elvis
?:
(بعداً در بخش کدها پوشش داده شد) - استفاده از برخی از توابع استاندارد Kotlin (که بعداً در نرم افزار کد پوشش داده شد)
در مورد ما، ما می دانیم که لیست کاربران نیازی به تهی شدن ندارد، زیرا بلافاصله پس از ساخت شی مقداردهی اولیه می شود، بنابراین می توانیم مستقیماً زمانی که شی را اعلام می کنیم نمونه سازی کنیم.
هنگام ایجاد نمونه هایی از انواع مجموعه، کاتلین چندین توابع کمکی را ارائه می دهد تا کد شما را خواناتر و انعطاف پذیرتر کند. در اینجا ما از یک MutableList
برای users
استفاده می کنیم:
private var users: MutableList<User>? = null
برای سادگی، میتوانیم از تابع mutableListOf()
استفاده کنیم، نوع عنصر لیست را ارائه کنیم، فراخوانی سازنده ArrayList
را از بلوک init
حذف کنیم، و اعلان نوع صریح ویژگی users
را حذف کنیم.
private val users = mutableListOf<User>()
ما همچنین var را به val تغییر دادیم زیرا کاربران دارای یک مرجع غیرقابل تغییر به لیست کاربران هستند. توجه داشته باشید که مرجع تغییرناپذیر است، اما خود فهرست قابل تغییر است (شما می توانید عناصر را اضافه یا حذف کنید).
با این تغییرات، ویژگی users
ما اکنون غیر پوچ است و می توانیم تمام موارد غیر ضروری را حذف کنیم !!
وقوع اپراتور
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
...
}
همچنین، از آنجایی که متغیر users از قبل مقداردهی شده است، باید مقدار اولیه را از بلوک init
حذف کنیم:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users.add(user1)
users.add(user2)
users.add(user3)
}
از آنجایی که هم lastName
و firstName
میتوانند null
باشند، باید زمانی که لیستی از نامهای کاربری قالببندی شده را میسازیم، قابلیت پوچپذیری را مدیریت کنیم. از آنجایی که میخواهیم "Unknown"
را در صورت عدم وجود هر یک از نامها نمایش دهیم، میتوانیم با حذف ?
از اعلامیه نوع
val name: String
اگر نام firstName
خالی است، name
یا lastName
یا "Unknown"
است:
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
این را می توان با استفاده از عملگر elvis ?:
به صورت اصطلاحی تر نوشت. اپراتور elvis عبارت سمت چپ خود را اگر null نباشد، یا عبارت سمت راست را، اگر سمت چپ آن null باشد، برمی گرداند.
بنابراین در کد زیر user.firstName
اگر null نباشد برگردانده می شود. اگر user.firstName
null باشد، عبارت در سمت راست مقدار "Unknown"
را برمیگرداند:
if (lastName != null) {
...
} else {
name = firstName ?: "Unknown"
}
7. قالب های رشته ای و if express
Kotlin کار با String
را با قالب های String آسان می کند. قالب های رشته ای به شما این امکان را می دهند که به متغیرهای داخل اعلان رشته ها ارجاع دهید.
مبدل خودکار الحاق نام و نام خانوادگی را برای ارجاع مستقیم به نام متغیر در رشته با استفاده از نماد $
به روز کرد و عبارت را بین { }
قرار داد.
// Java
name = user.getFirstName() + " " + user.getLastName();
// Kotlin
name = "${user.firstName} ${user.lastName}"
در کد، الحاق رشته را با:
name = "$firstName $lastName"
در Kotlin if
, when
, for
, و while
عباراتی هستند - مقداری را برمی گردانند. IDE شما حتی یک اخطار نشان می دهد که تخصیص باید از این موارد حذف if
:
بیایید پیشنهاد IDE را دنبال کنیم و تخصیص را برای هر دو دستور if
حذف کنیم. آخرین خط دستور if اختصاص داده می شود. مانند این، واضحتر است که تنها هدف این بلوک، مقداردهی نام است:
name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
در مرحله بعد، اخطاری دریافت میکنیم مبنی بر اینکه میتوان اعلان name
را به تکلیف ملحق کرد. بیایید این را نیز اعمال کنیم. از آنجایی که نوع متغیر نام قابل استنباط است، میتوانیم اعلان نوع صریح را حذف کنیم. اکنون formattedUserNames
ما به شکل زیر است:
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
8. عملیات روی مجموعه ها
بیایید نگاهی دقیقتر به formattedUserNames
UserNames بگیریم و ببینیم چگونه میتوانیم آن را اصطلاحیتر کنیم. در حال حاضر کد به صورت زیر عمل می کند:
- یک لیست جدید از رشته ها ایجاد می کند
- از طریق لیست کاربران تکرار می شود
- نام قالب بندی شده را برای هر کاربر بر اساس نام و نام خانوادگی کاربر می سازد
- لیست تازه ایجاد شده را برمی گرداند
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
}
}
برای سادهتر کردن این موضوع، میتوانیم متغیر 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
}
مشکل اینجاست که با بازگشت users
، هر مصرف کننده ای از کلاس Repository می تواند لیست کاربران ما را تغییر دهد - ایده خوبی نیست! بیایید با استفاده از یک ویژگی پشتیبان این مشکل را برطرف کنیم.
ابتدا اجازه دهید نام users
را به _users
تغییر دهیم. اکنون یک ویژگی عمومی غیرقابل تغییر اضافه کنید که لیستی از کاربران را برمی گرداند. بیایید آن را users
:
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
با این تغییر، ویژگی private _users
به ویژگی پشتوانه برای ویژگی عمومی users
تبدیل می شود. خارج از کلاس Repository
، لیست _users
قابل تغییر نیست، زیرا مصرف کنندگان کلاس فقط از طریق users
می توانند به لیست دسترسی داشته باشند.
کد کامل:
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
10. توابع و ویژگی های سطح بالا و پسوند
در حال حاضر کلاس Repository
می داند که چگونه نام کاربری فرمت شده را برای یک شی User
محاسبه کند. اما اگر بخواهیم از همان منطق قالببندی مجدد در کلاسهای دیگر استفاده کنیم، باید آن را کپی و پیست کنیم یا به کلاس User
منتقل کنیم.
Kotlin توانایی اعلام توابع و خصوصیات خارج از هر کلاس، شی یا رابط را فراهم می کند. به عنوان مثال، تابع mutableListOf()
که ما برای ایجاد یک نمونه جدید از یک List
استفاده کردیم، مستقیماً در Collections.kt
از کتابخانه استاندارد تعریف شده است.
در جاوا، هر زمان که به برخی از قابلیت های کاربردی نیاز داشتید، به احتمال زیاد یک کلاس Util
ایجاد می کنید و آن عملکرد را به عنوان یک تابع ثابت اعلام می کنید. در Kotlin می توانید توابع سطح بالا را بدون داشتن کلاس اعلام کنید. با این حال، Kotlin همچنین توانایی ایجاد توابع افزونه را فراهم می کند. اینها توابعی هستند که نوع خاصی را گسترش می دهند اما خارج از نوع اعلام می شوند. به این ترتیب، آنها به آن نوع تمایل دارند.
مشاهده توابع و خصوصیات افزونه را می توان با استفاده از اصلاح کننده های دید محدود کرد. اینها استفاده را فقط به کلاس هایی محدود می کنند که به پسوند نیاز دارند و فضای نام را آلوده نمی کنند.
برای کلاس User
، میتوانیم یک تابع پسوند اضافه کنیم که نام قالببندی شده را محاسبه میکند، یا میتوانیم نام قالبشده را در یک ویژگی پسوند نگه داریم. می توان آن را خارج از کلاس Repository
، در همان فایل اضافه کرد:
// extension function
fun User.getFormattedName(): String {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// extension property
val User.userFormattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName
سپس میتوانیم از توابع و ویژگیهای افزونه استفاده کنیم که گویی بخشی از کلاس User
هستند.
از آنجایی که نام فرمت شده یک ویژگی کاربر است و نه عملکرد کلاس Repository
، اجازه دهید از ویژگی پسوند استفاده کنیم. فایل Repository
ما اکنون به شکل زیر است:
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user -> user.formattedName }
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
کتابخانه استاندارد Kotlin از توابع افزونه برای گسترش عملکرد چندین API جاوا استفاده می کند. بسیاری از قابلیت های Iterable
و Collection
به عنوان توابع افزونه پیاده سازی می شوند. به عنوان مثال، تابع map
که در مرحله قبل استفاده کردیم، یک تابع پسوندی در Iterable
است.
11. توابع محدوده: let, application, with, run, also
در کد کلاس Repository
خود، چندین شیء کاربر را به لیست _users
اضافه می کنیم. این تماس ها را می توان با کمک توابع scope اصطلاحی تر کرد.
برای اجرای کد فقط در زمینه یک شی خاص، بدون نیاز به دسترسی به شی بر اساس نام آن، کاتلین 5 تابع محدوده ایجاد کرد: let
, apply
, with
, run
و also
. کوتاه و قدرتمند، همه این توابع دارای یک گیرنده ( this
)، ممکن است یک آرگومان ( it
) داشته باشند و ممکن است یک مقدار برگردانند. بسته به هدفی که می خواهید به دست آورید، تصمیم می گیرید از کدام یک استفاده کنید.
در اینجا یک برگه تقلب مفید وجود دارد که به شما کمک می کند این را به خاطر بسپارید:
از آنجایی که ما در حال پیکربندی شی _users
خود در Repository
خود هستیم، میتوانیم کد را با apply
از تابع application اصطلاحیتر کنیم:
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. بسته شدن
در این کد لبه، اصولی را که برای شروع دوباره سازی کد خود از جاوا به کاتلین نیاز دارید، توضیح دادیم. این refactoring مستقل از پلتفرم توسعه شماست و به اطمینان از اصطلاحی بودن کدی که می نویسید کمک می کند.
Kotlin اصطلاحی نوشتن کد را کوتاه و شیرین می کند. با تمام ویژگیهایی که Kotlin ارائه میکند، راههای زیادی برای ایمنتر، مختصرتر و خواناتر کردن کد شما وجود دارد. به عنوان مثال، حتی میتوانیم کلاس Repository
خود را با نمونهسازی فهرست _users
با کاربران مستقیماً در اعلان، بهینهسازی کنیم و از شر بلوک init
خلاص شویم:
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
ما مجموعه وسیعی از موضوعات را پوشش دادیم، از مدیریت nullability، تکتنها، رشتهها و مجموعهها گرفته تا موضوعاتی مانند توابع افزونه، توابع سطح بالا، ویژگیها و توابع دامنه. ما از دو کلاس جاوا به دو کلاس کاتلین رفتیم که اکنون به شکل زیر هستند:
User.kt
class User(var firstName: String?, var lastName: String?)
Repository.kt
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() = _users.map { user -> user.formattedName }
}
در اینجا یک TL;DR از عملکردهای جاوا و نگاشت آنها به Kotlin آمده است:
جاوا | کاتلین |
شی | شی |
| |
| |
کلاسی که فقط داده ها را نگه می دارد | کلاس |
مقداردهی اولیه در سازنده | مقداردهی اولیه در بلوک |
فیلدها و توابع | فیلدها و توابع اعلام شده در یک |
کلاس سینگلتون | |
برای کسب اطلاعات بیشتر در مورد Kotlin و نحوه استفاده از آن در پلتفرم خود، این منابع را بررسی کنید:
- کاتلین کوانس
- آموزش های کاتلین
- توسعه برنامه های اندروید با Kotlin - دوره رایگان
- Kotlin Bootcamp برای برنامه نویسان
- Kotlin برای توسعه دهندگان جاوا - دوره رایگان در حالت حسابرسی