1. مرحبًا
في هذا الدليل التعليمي حول الرموز البرمجية، ستتعرّف على كيفية تحويل الرمز البرمجي من Java إلى Kotlin. ستتعرّف أيضًا على اصطلاحات لغة Kotlin وكيفية التأكّد من أنّ الرمز البرمجي الذي تكتبه يراعيها.
يناسب هذا الدرس التطبيقي حول الترميز أي مطوّر يستخدم Java ويفكر في نقل مشروعه إلى Kotlin. سنبدأ ببعض فئات Java التي ستحوّلها إلى Kotlin باستخدام IDE. بعد ذلك، سنلقي نظرة على الرمز البرمجي المحوَّل ونرى كيف يمكننا تحسينه من خلال جعله أكثر متوافقًا مع قواعد اللغة وتجنُّب الأخطاء الشائعة.
المُعطيات
ستتعرّف على كيفية تحويل Java إلى Kotlin. وخلال ذلك، ستتعرّف على ميزات لغة Kotlin ومفاهيمه التالية:
- التعامل مع إمكانية قبول القيم الفارغة
- تنفيذ العناصر الفردية
- فئات البيانات
- التعامل مع السلاسل
- عامل Elvis
- إزالة البنية
- المواقع ومواقع الدعم
- الوسيطات التلقائية والمَعلمات المُعنوَنة
- العمل مع المجموعات
- دوال الإضافات
- الدوال والمَعلمات على مستوى أعلى
- الكلمات الرئيسية
let
وapply
وwith
وrun
الافتراضات
من المفترض أن تكون على دراية بلغة Java.
المتطلبات
2- الإعداد
إنشاء مشروع جديد
إذا كنت تستخدم IntelliJ IDEA، أنشئ مشروع Java جديدًا باستخدام Kotlin/JVM.
إذا كنت تستخدم "استوديو Android"، أنشئ مشروعًا جديدًا باستخدام نموذج ما مِن نشاط. اختَر Kotlin كلغة المشروع. يمكن أن يكون الحد الأدنى لإصدار حزمة تطوير البرامج (SDK) أي قيمة، ولن يؤثر ذلك في النتيجة.
الرمز
سننشئ نموذجًا لكائن User
وفئة 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;
}
}
سيُعلمك محرّر بيئة التطوير المتكامل بأنّ @Nullable
غير محدّد. لذلك، استورِد androidx.annotation.Nullable
إذا كنت تستخدم "استوديو Android" أو org.jetbrains.annotations.Nullable
إذا كنت تستخدم IntelliJ.
أنشئ ملفًا جديدًا باسم 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 وnullability
يمكن لبيئة تطوير البرامج المتكاملة (IDE) لدينا تحويل رمز Java إلى رمز Kotlin تلقائيًا بشكل جيد، ولكن في بعض الأحيان تحتاج إلى مساعدة بسيطة. لنسمح لأداة تطوير البرامج (IDE) بإجراء جولة أولية في عملية التحويل. بعد ذلك، سنراجع الرمز الناتج لمعرفة كيفية تحويله بهذه الطريقة وسبب ذلك.
انتقِل إلى ملف User.java
واحوِله إلى ملف Kotlin: شريط القوائم -> الرمز البرمجي -> تحويل ملف Java إلى ملف Kotlin.
إذا طلب منك بيئة تطوير البرامج (IDE) تصحيح الأخطاء بعد التحويل، اضغط على نعم.
من المفترض أن يظهر لك رمز Kotlin التالي:
class User(var firstName: String?, var lastName: String?)
يُرجى العلم أنّه تمت إعادة تسمية User.java
إلى User.kt
. تحتوي ملفات Kotlin على الامتداد .kt.
في فئة Java User
، كانت لدينا سمتان: firstName
وlastName
. وكان لكل منها طريقة للحصول على القيمة وضبطها، ما يجعل قيمتها قابلة للتغيير. الكلمة الرئيسية في Kotlin للمتغيّرات القابلة للتغيير هي var
، لذا يستخدم المحوِّل var
لكلّ من هذه السمات. إذا كانت سمات Java تتضمّن أدوات جلب فقط، ستكون للقراءة فقط وسيتمّ تعريفها على أنّها متغيّرات val
. تشبه val
الكلمة الرئيسية final
في Java.
من الاختلافات الرئيسية بين Kotlin وJava أنّ Kotlin تحدّد صراحةً ما إذا كان المتغيّر يمكنه قبول قيمة فارغة. ويتم ذلك من خلال إلحاق ?
ببيان النوع.
بما أنّنا وضعنا علامة nullable على firstName
وlastName
، وضع المحوِّل التلقائي علامة nullable على السمتَين تلقائيًا باستخدام String?
. إذا أضفت تعليقًا توضيحيًا على عناصر Java على أنّها غير فارغة (باستخدام 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?)
يمكن أن تحتوي لغة Kotlin، مثل Java، على دالة أساسية واحدة ودالة أساسية واحدة أو أكثر. إنّ الأسلوب الوارد في المثال أعلاه هو المُنشئ الأساسي لفئة User
. إذا كنت تحوّل فئة Java تحتوي على وظائف إنشاء متعددة، سينشئ المحوِّل تلقائيًا وظائف إنشاء متعددة في Kotlin أيضًا. ويتم تحديدها باستخدام الكلمة الرئيسية constructor
.
إذا أردنا إنشاء مثيل لهذه الفئة، يمكننا إجراء ذلك على النحو التالي:
val user1 = User("Jane", "Doe")
المساواة
تتضمّن Kotlin نوعَين من المساواة:
- يستخدم تكافؤ البنية عامل التشغيل
==
ويُطلِب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. في ملف الترميز، نريد تحديد الاسم الأول واسم العائلة دائمًا في بيان عنصر User
، لذلك لا نحتاج إلى قيم تلقائية.
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)
}
}
لنطّلِع على الإجراءات التي اتّخذها المحوِّل التلقائي:
- قائمة
users
غير قابلة للتحديد لأنّه لم يتم إنشاء مثيل للكائن في وقت التعريف. - يتم تعريف الدوالّ في Kotlin، مثل
getUsers()
، باستخدام المُعدِّلfun
. - أصبحت طريقة
getFormattedUserNames()
الآن خاصية باسمformattedUserNames
. - التكرار في قائمة المستخدمين (التي كانت في البداية جزءًا من
getFormattedUserNames(
) له بنية مختلفة عن بنية Java - أصبح حقل
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
السمات والطُرق في Kotlin
في Java، نستخدم الكلمة الرئيسية static
للحقول أو الدوال للإشارة إلى أنّها تنتمي إلى فئة ولكن ليس إلى مثيل من الفئة. لهذا السبب، أنشأنا الحقل الثابت INSTANCE
في فئة Repository
. ويتمثل مكافئ Kotlin في العنصر companion object
. يمكنك أيضًا هنا تحديد الحقول الثابتة والدوالّ الثابتة. أنشأ المحوِّل كتلة العنصر المصاحب ونقل الحقل INSTANCE
إلى هنا.
معالجة القيم الفردية
بما أنّنا نحتاج إلى نسخة افتراضية واحدة فقط من فئة Repository
، استخدمنا نمط العنصر الفردي في Java. باستخدام 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، جعل المحوِّل التلقائي قائمة المستخدمين nullable، لأنّه لم يتمّ إعدادها كعنصر عند الإعلان عنها. ونتيجةً لذلك، يجب استخدام عامل التشغيل !!
لتأكيد عدم القيمة الخالية في جميع استخدامات عنصر users
. (ستظهر لك users!!
وuser!!
في الرمز المحوَّل). يحوّل عامل التشغيل !!
أي متغيّر إلى نوع غير فارغ، حتى تتمكّن من الوصول إلى السمات أو استدعاء الدوالّ عليه. ومع ذلك، سيتم طرح استثناء إذا كانت قيمة المتغيّر فارغة. باستخدام !!
، أنت تخاطر بظهور استثناءات أثناء وقت التشغيل.
بدلاً من ذلك، يُفضَّل التعامل مع قيمة العدم باستخدام إحدى الطريقتَين التاليتَين:
- إجراء عملية التحقّق من القيمة الخالية (
if (users != null) {...}
) - استخدام عامل التشغيل elvis
?:
(سيتم تناوله لاحقًا في الدرس التطبيقي) - استخدام بعض الدوالّ العادية في Kotlin (سيتم تناولها لاحقًا في ورشة رموز البرامج)
في حالتنا، نعلم أنّه ليس من الضروري أن تكون قائمة المستخدمين nullable، لأنّه يتمّ إعدادها مباشرةً بعد إنشاء العنصر (في البنية init
). وبالتالي، يمكننا إنشاء مثيل لعنصر users
مباشرةً عند تحديده.
عند إنشاء نُسخ من أنواع المجموعات، توفّر Kotlin عدة دوال مساعدة لجعل رمزك أكثر سهولة ومرونة. في ما يلي مثال على استخدام 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
غير فارغ، ويمكننا إزالة كل مرّات ظهور عامل التشغيل !!
غير الضرورية. يُرجى العِلم أنّه سيستمر ظهور أخطاء الترجمة في "استوديو Android"، ولكن عليك مواصلة الخطوات القليلة التالية في ورشات رموز البرامج لحلّها.
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)
إعادة التنظيم
تسمح Kotlin بتحليل كائن إلى عدد من المتغيّرات باستخدام بنية جملة تُعرف باسم بيان تحليل البنية. ننشئ متغيّرات متعدّدة ويمكننا استخدامها بشكل مستقل.
على سبيل المثال، تتيح فئات 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)
}
if expression
إنّ الأسماء الواردة في قائمة userNames ليست بالشكل المطلوب بعد. بما أنّ كلاً من lastName
وfirstName
يمكن أن يكون null
، علينا التعامل مع إمكانية عدم توفّر القيمة عند إنشاء قائمة بأسماء المستخدمين المنسَّقة. نريد عرض "Unknown"
إذا لم يكن أي من الاسمَين متوفّرًا. بما أنّ المتغيّر name
لن يتم تغييره بعد ضبطه مرة واحدة، يمكننا استخدام val
بدلاً من var
. عليك إجراء هذا التغيير أولاً.
val name: String
اطّلِع على الرمز الذي يضبط متغيّر الاسم. قد يبدو لك الأمر جديدًا أن يتم ضبط متغيّر ليكون مساويًا لوحدة رمز if
/ else
. يُسمح بذلك لأنّ if
وwhen
هما تعبيران في Kotlin، ويعرضان قيمة. سيتمّ إسناد السطر الأخير من عبارة 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 ?:
. سيُرجِع عامل elvis التعبير على الجانب الأيسر إذا لم يكن فارغًا، أو التعبير على الجانب الأيمن إذا كان الجانب الأيسر فارغًا.
وبالتالي، في الرمز التالي، يتم عرض firstName
إذا لم تكن قيمة فارغة. إذا كانت firstName
فارغة، يعرض التعبير القيمة على يسار "Unknown"
:
name = if (lastName != null) {
...
} else {
firstName ?: "Unknown"
}
7. نماذج السلاسل
تسهّل Kotlin العمل مع String
باستخدام نماذج السلاسل. تسمح لك نماذج السلاسل بالإشارة إلى المتغيّرات داخل تعريفات السلاسل باستخدام الرمز $ قبل المتغيّر. يمكنك أيضًا وضع تعبير ضمن تعريف سلسلة، وذلك عن طريق وضع التعبير ضمن { } واستخدام الرمز $ قبله. مثال: ${user.firstName}
يستخدم الرمز البرمجي حاليًا تسلسل سلاسل لجمع firstName
وlastName
في اسم المستخدم.
if (firstName != null) {
firstName + " " + lastName
}
بدلاً من ذلك، استبدِل تسلسل السلسلة بما يلي:
if (firstName != null) {
"$firstName $lastName"
}
يمكن أن يؤدي استخدام نماذج السلاسل إلى تبسيط الرمز البرمجي.
سيعرض لك بيئة التطوير المتكاملة تحذيرات إذا كانت هناك طريقة أكثر شيوعًا لكتابة الرمز البرمجي. سيظهر لك خط سفلي متعرج في الرمز، وعند تمرير مؤشر الماوس فوقه، سيظهر لك اقتراح حول كيفية إعادة صياغة الرمز.
من المفترض أن يظهر لك حاليًا تحذير بأنّه يمكن ربط بيان 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>
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
}
}
يُرجى العِلم أنّنا نستخدم مشغل Elvis لعرض "Unknown"
إذا كان user.lastName
فارغًا، لأنّ 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
تتضمّن دالة جلب مخصّصة. في الخلفية، لا تزال Kotlin تنشئ دالة getFormattedUserNames()
تعرض List
.
في Java، يمكننا عرض سمات الفئة من خلال وظائف getter وsetter. تسمح لنا Kotlin بالتمييز بشكل أفضل بين سمات الفئة، التي يتم التعبير عنها باستخدام الحقول، والوظائف، أي الإجراءات التي يمكن للفئة تنفيذها، والتي يتم التعبير عنها باستخدام الدوالّ. في حالتنا، تكون فئة Repository
بسيطة جدًا ولا تُجري أي إجراءات، لذا تحتوي على حقول فقط.
يتم الآن تشغيل المنطق الذي تم تشغيله في دالة getFormattedUserNames()
في Java عند استدعاء دالة الحصول على سمة formattedUserNames
في Kotlin.
على الرغم من أنّنا لا نملك حقلًا يتوافق صراحةً مع السمة formattedUserNames
، توفّر لنا Kotlin حقلًا احتياطيًا تلقائيًا باسم field
يمكننا الوصول إليه عند الحاجة من خلال وظائف الحصول على القيم وضبطها المخصّصة.
في بعض الأحيان، نحتاج إلى بعض الوظائف الإضافية التي لا يوفّرها الحقل الاحتياطي التلقائي.
لنطّلِع على مثال.
داخل فئة Repository
، لدينا قائمة قابلة للتغيير بالمستخدمين يتم عرضها في الدالة getUsers()
التي تم إنشاؤها من رمز Java البرمجي:
fun getUsers(): List<User>? {
return users
}
لأنّنا لم نريد أن يعدّل المُتصلون بفئة Repository
قائمة المستخدمين، أنشأنا الدالة getUsers()
التي تعرض List<User>
للقراءة فقط. في Kotlin، نفضّل استخدام السمات بدلاً من الدوالّ في مثل هذه الحالات. بعبارة أدق، سنعرض List<User>
للقراءة فقط يكون مدعومًا بـ mutableListOf<User>
.
أولاً، لنعدّل اسم users
إلى _users
. ميِّز اسم المتغيّر وانقر عليه بزر الماوس الأيمن لإعادة صياغة > إعادة تسمية المتغيّر. بعد ذلك، أضِف موقعًا متاحًا للجميع للقراءة فقط يعرض قائمة بالمستخدمين. لنفترض أنّه users
:
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
في هذه المرحلة، يمكنك حذف طريقة getUsers()
.
بعد إجراء التغيير أعلاه، يصبح الموقع الإلكتروني الخاص _users
هو الموقع الإلكتروني الأساسي للموقع الإلكتروني العلني users
. خارج فئة Repository
، لا يمكن تعديل قائمة _users
، لأنّ مستخدِمي الفئة لا يمكنهم الوصول إلى القائمة إلا من خلال users
.
عند استدعاء users
من رمز Kotlin، يتم استخدام تنفيذ List
من مكتبة Kotlin العادية، حيث لا يمكن تعديل القائمة. في حال استدعاء users
من Java، يتم استخدام تنفيذ 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 العادية.
في Java، عندما تحتاج إلى بعض وظائف المرافق، من المرجّح أن تنشئ فئة 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 العادية دوال الإضافة لتوسيع وظائف العديد من واجهات برمجة تطبيقات Java، ويتم تنفيذ الكثير من الوظائف في Iterable
وCollection
كدوال إضافة. على سبيل المثال، الدالة map
التي استخدمناها في خطوة سابقة هي دالة تمديد في Iterable
.
11. دوال النطاق: let وapply وwith وrun وalso
في رمز الفئة Repository
، نضيف عدة عناصر User
إلى قائمة _users
. يمكن جعل هذه الطلبات أكثر تعبيرية باستخدام دوالّ نطاق Kotlin.
لتنفيذ الرمز البرمجي في سياق عنصر معيّن فقط، بدون الحاجة إلى الوصول إلى العنصر استنادًا إلى اسمه، تقدّم Kotlin 5 وظائف نطاق: let
وapply
وwith
وrun
وalso
. تجعل هذه الدوالّ رمزك البرمجي أسهل في القراءة وأكثر إيجازًا. تحتوي جميع دوال النطاق على مستلِم (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. الخاتمة
في هذا الدرس التطبيقي حول الترميز، غطّينا الأساسيات التي تحتاج إليها لبدء تحويل الرمز البرمجي من Java إلى Kotlin. لا تعتمد عملية التحويل هذه على منصّة التطوير، وتساعد في ضمان أنّ الرمز البرمجي الذي تكتبه هو رمز Kotlin عادي.
تجعل لغة Kotlin الأسلوبية كتابة الرموز البرمجية قصيرة وواضحة. مع كل الميزات التي تقدّمها Kotlin، هناك العديد من الطرق لجعل رمزك البرمجي أكثر أمانًا وأكثر إيجازًا وأكثر سهولة في القراءة. على سبيل المثال، يمكننا أيضًا تحسين فئة Repository
من خلال إنشاء مثيل لقائمة _users
باستخدام المستخدمين مباشرةً في البيان، والتخلص من القسم init
:
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
لقد تناولنا مجموعة كبيرة من المواضيع، بدءًا من التعامل مع ما إذا كان العنصر قابلاً للقيمة الخالية أو لا، والعناصر الفردية، والسلاسل، والمجموعات، وصولاً إلى مواضيع مثل دوالّ الإضافات والدوالّ ذات المستوى الأعلى والسمات ودوالّ النطاق. لقد استبدلنا فئتَي Java بفئتَي Kotlin تبدوان الآن على النحو التالي:
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 }
}
في ما يلي ملخّص لوظائف Java وربطها بلغة Kotlin:
Java | Kotlin |
عنصر | عنصر |
|
|
|
|
فئة تخزّن البيانات فقط | الصف |
الإعداد في الدالة الإنشائية | الإعداد في مجموعة |
| الحقول والدوالّ التي تمّ تعريفها في |
فئة Singleton |
|
للاطّلاع على مزيد من المعلومات حول Kotlin وكيفية استخدامها على منصتك، اطّلِع على هذه المراجع:
- Kotlin Koans
- برامج تعليمية حول لغة Kotlin
- أساسيات لغة Kotlin لنظام التشغيل Android
- برنامج تدريبي حول لغة Kotlin للمبرمجين
- Kotlin لمطوّري Java: دورة تدريبية مجانية في وضع "التدقيق"