تعلَّم لغة البرمجة Kotlin.

Kotlin هي لغة برمجة يستخدمها مطوّرو تطبيقات Android على نطاق واسع في كل مكان. هذا الموضوع بمثابة دورة تدريبية في الكوتلين تساعدك في إعدادك للعمل بسرعة.

تعريف المتغيّر

تستخدم لغة Kotlin كلمتين رئيسيتين مختلفتين للإشارة إلى المتغيّرات: val وvar.

  • استخدِم val للمتغيّر الذي لا تتغير قيمته أبدًا. لا يمكنك إعادة تعيين قيمة لمتغير تم تعريفه باستخدام val.
  • استخدِم var لمتغيّر يمكن تغيير قيمته.

في المثال أدناه، count هو متغيّر من النوع Int تم تخصيص قيمة مبدئية له لـ 10:

var count: Int = 10

Int هو نوع يمثّل عددًا صحيحًا، وهو أحد الأنواع العددية العديدة التي يمكن تمثيلها في لغة Kotlin. على غرار اللغات الأخرى، يمكنك أيضًا استخدام Byte وShort وLong وFloat وDouble بناءً على بياناتك الرقمية.

تعني الكلمة الرئيسية var أنه يمكنك إعادة تعيين قيم للسمة count حسب الحاجة. على سبيل المثال، يمكنك تغيير قيمة count من 10 إلى 15:

var count: Int = 10
count = 15

ومع ذلك، لا يُقصد تغيير بعض القيم. ننصحك باستخدام String بعنوان languageName. إذا أردت التأكّد من أنّ القيمة languageName تتضمّن دائمًا قيمة "Kotlin"، يمكنك إعلان languageName باستخدام الكلمة الرئيسية val:

val languageName: String = "Kotlin"

تتيح لك هذه الكلمات الرئيسية أن توضح لك بوضوح ما يمكن تغييره. استخدمها لصالحك حسب الحاجة. إذا كان يجب إعادة تعيين مرجع متغيّر، يجب تعريفه بأنّه var. ويمكنك بدلاً من ذلك استخدام val.

استنتاج النوع

وبمتابعة المثال السابق، عند تعيين قيمة مبدئية إلى languageName، يمكن للمحول البرمجي لـ Kotlin استنتاج النوع استنادًا إلى نوع القيمة المعينة.

بما أنّ قيمة "Kotlin" من النوع String، يستنتج المحول البرمجي أنّ languageName هي أيضًا String. تجدر الإشارة إلى أنّ Kotlin هي لغة مكتوبة بشكل ثابت. وهذا يعني أنه يتم حل النوع في وقت التجميع ولا يتغير أبدًا.

في المثال التالي، يتم استنتاج languageName على أنّها String، لذا لا يمكنك استدعاء أي دوال ليست جزءًا من الفئة String:

val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()

// Fails to compile
languageName.inc()

toUpperCase() هي دالة لا يمكن طلبها إلا في متغيّرات من النوع String. وبما أنّ المحول البرمجي لـ Kotlin قد استنتج أنّ languageName هو String، يمكنك الاتصال بـ toUpperCase() بأمان. ومع ذلك، فإن inc() هي دالة عامل تشغيل Int، لذا لا يمكن استدعائها في String. يمنحك نهج Kotlin في استنتاج الكتابة لكل من الإيجاز وأمان الكتابة.

قيمة فارغة

في بعض اللغات، يمكن تعريف متغيّر نوع المرجع بدون توفير قيمة أولية صريحة. في هذه الحالات، عادةً ما تحتوي المتغيرات على قيمة فارغة. لا يمكن أن تحتفظ متغيّرات لغة Kotlin بقيم فارغة تلقائيًا. يعني ذلك أنّ المقتطف التالي غير صالح:

// Fails to compile
val languageName: String = null

لكي يحتفظ المتغير بقيمة فارغة، يجب أن يكون من النوع nullable. يمكنك تحديد متغيّر على أنّه قابل للقيم الفارغة من خلال لاحقة نوعه بسمة ?، كما هو موضّح في المثال التالي:

val languageName: String? = null

باستخدام النوع String?، يمكنك تعيين قيمة String أو null إلى languageName.

يجب التعامل بحرص مع المتغيّرات القابلة للقيم الفارغة أو المخاطرة باحتمالية NullPointerException. في لغة Java، على سبيل المثال، إذا حاولت استدعاء طريقة على قيمة فارغة، يتعطل برنامجك.

توفر لغة Kotlin عددًا من الآليات للعمل بأمان مع المتغيرات القابلة للقيم الفارغة. لمزيد من المعلومات، يُرجى الاطّلاع على أنماط لغة Kotlin الشائعة في Android: قابلية الحذف.

العبارات الشرطية

تتميز لغة Kotlin بعدة آليات لتنفيذ المنطق الشرطي. وأكثرها شيوعًا هي عبارة if-else. إذا تم تقييم تعبير ملتف بين قوسين بجانب كلمة رئيسية if بقيمة true، سيتم تنفيذ الرمز البرمجي ضمن ذلك الفرع (أي الرمز الذي يليه مباشرة ملفوف بأقواس معقوفة). وبخلاف ذلك، يتم تنفيذ الرمز داخل فرع else.

if (count == 42) {
    println("I have the answer.")
} else {
    println("The answer eludes me.")
}

يمكنك تمثيل شروط متعددة باستخدام else if. يتيح لك هذا تمثيل منطق أكثر دقة وتعقيدًا ضمن عبارة شرطية واحدة، كما هو موضح في المثال التالي:

if (count == 42) {
    println("I have the answer.")
} else if (count > 35) {
    println("The answer is close.")
} else {
    println("The answer eludes me.")
}

تعد العبارات الشرطية مفيدة لتمثيل منطق الحالة، ولكن قد تجد أنك تكرر نفسك عند كتابتها. في المثال أعلاه، ما عليك سوى طباعة String في كل فرع. لتجنُّب هذا التكرار، تقدّم لغة Kotlin تعبيرات شرطية. يمكن إعادة كتابة المثال الأخير على النحو التالي:

val answerString: String = if (count == 42) {
    "I have the answer."
} else if (count > 35) {
    "The answer is close."
} else {
    "The answer eludes me."
}

println(answerString)

ضمنيًا، يعرض كل فرع شرطي نتيجة التعبير في السطر الأخير، وبالتالي لن تحتاج إلى استخدام الكلمة الرئيسية return. بما أنّ نتيجة الفروع الثلاثة هي من النوع String، تكون نتيجة التعبير if-else أيضًا من النوع String. في هذا المثال، يتم تعيين قيمة أولية لـ answerString من نتيجة تعبير if-else. يمكن استخدام استنتاج النوع لحذف تعريف النوع الصريح لـ answerString، ولكن غالبًا ما يكون من الجيد تضمينه من أجل الوضوح.

ومع ازدياد مدى تعقيد جملتك الشرطية، يمكنك التفكير في استبدال تعبير if-else بتعبير when، كما هو موضح في المثال التالي:

val answerString = when {
    count == 42 -> "I have the answer."
    count > 35 -> "The answer is close."
    else -> "The answer eludes me."
}

println(answerString)

يتم تمثيل كل فرع في تعبير when بشرط وسهم (->) ونتيجة. إذا تم تقييم الشرط الموجود في الجانب الأيسر من السهم بأنه صحيح، فسيتم عرض نتيجة التعبير في الجانب الأيمن. تجدر الإشارة إلى أن عملية التنفيذ لا تتم من فرع إلى آخر. إنّ الرمز البرمجي في مثال تعبير when مكافئ من الناحية الوظيفية لذلك المثال الوارد في المثال السابق، لكن يمكن القول أنّه أسهل في القراءة.

تُبرز بنود الخدمة الشرطية في Kotlin واحدة من ميزاتها الأكثر فعالية، وهي البث الذكي. بدلاً من استخدام عامل التشغيل الآمن أو عامل التأكيد غير الفارغة للعمل على القيم القابلة للقيم الفارغة، يمكنك بدلاً من ذلك التحقق مما إذا كان المتغير يحتوي على مرجع إلى قيمة فارغة باستخدام عبارة شرطية، كما هو موضح في المثال التالي:

val languageName: String? = null
if (languageName != null) {
    // No need to write languageName?.toUpperCase()
    println(languageName.toUpperCase())
}

ضمن الفرع الشرطي، يمكن التعامل مع languageName على أنّها غير قابلة للقيم الفارغة. تتميز لغة Kotlin بالذكاء الكافي لإدراك أنّ شرط تنفيذ الفرع هو أنّ languageName لا يتضمّن قيمة فارغة، لذلك لن تحتاج إلى التعامل مع languageName على أنّه قابل للقيم الفارغة ضمن ذلك الفرع. يمكن استخدام ميزة "البث الذكي" هذه مع عمليات التحقّق الخالية من البيانات أو عمليات التحقّق من الكتابة أو أي شرط يستوفي عقد.

الدوال

يمكنك تجميع تعبير واحد أو أكثر في دالة. بدلاً من تكرار نفس سلسلة التعبيرات في كل مرة تحتاج فيها إلى نتيجة، يمكنك التفاف التعبيرات في دالة واستدعاء هذه الدالة بدلاً من ذلك.

لتعريف دالة، استخدم الكلمة الرئيسية fun متبوعة باسم الدالة. بعد ذلك، حدد أنواع المدخلات التي تأخذها الدالة، إن وجدت، وأعلن عن نوع المخرجات التي تعرضها. نص الدالة هو المكان الذي تُحدد فيه التعبيرات التي يتم استدعاؤها عند استدعاء الدالة.

وبناءً على الأمثلة السابقة، إليك دالة Kotlin الكاملة:

fun generateAnswerString(): String {
    val answerString = if (count == 42) {
        "I have the answer."
    } else {
        "The answer eludes me"
    }

    return answerString
}

الدالة في المثال أعلاه لها الاسم generateAnswerString. إنه لا يأخذ أي مدخلات. تنتج نتيجة من النوع String. لاستدعاء دالة، استخدِم اسمها متبوعًا بعامل تشغيل الاستدعاء (()). في المثال أدناه، يتم إعداد المتغيّر answerString مع النتيجة من generateAnswerString().

val answerString = generateAnswerString()

يمكن أن تستخدم الدوال الوسيطات كإدخال، كما هو موضَّح في المثال التالي:

fun generateAnswerString(countThreshold: Int): String {
    val answerString = if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }

    return answerString
}

عند تعريف دالة، يمكنك تحديد أي عدد من الوسيطات وأنواعها. في المثال أعلاه، يستخدم generateAnswerString() وسيطة واحدة باسم countThreshold من النوع Int. داخل الدالة، يمكنك الرجوع إلى الوسيطة باستخدام اسمها.

عند استدعاء هذه الدالة، يجب عليك تضمين وسيطة داخل أقواس استدعاء الدالة:

val answerString = generateAnswerString(42)

تبسيط تعريف الدوال

تعد generateAnswerString() دالة بسيطة إلى حد ما. تعلن الدالة عن متغير ثم تعود على الفور. عند عرض نتيجة تعبير واحد من إحدى الدوال، يمكنك تخطي إعلان متغير محلي من خلال عرض نتيجة التعبير if-else المضمّن في الدالة مباشرةً، كما هو موضح في المثال التالي:

fun generateAnswerString(countThreshold: Int): String {
    return if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }
}

يمكنك أيضًا استبدال الكلمة الرئيسية المعروضة بعامل تشغيل التعيين:

fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
        "I have the answer"
    } else {
        "The answer eludes me"
    }

الدوال المجهولة

لا تحتاج كل دالة إلى اسم. يتم تحديد بعض الدوال بشكل مباشر من خلال مدخلاتها ومخرجاتها. تُسمى هذه الدوال الدوال المجهولة. يمكنك الاحتفاظ بمرجع لدالة مجهولة، باستخدام هذا المرجع لاستدعاء الدالة المجهولة لاحقًا. يمكنك أيضًا تمرير المرجع حول تطبيقك، كما هو الحال مع أنواع المراجع الأخرى.

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

مثل الدوال المُسمّاة، يمكن أن تحتوي الدوال المجهولة على أي عدد من التعبيرات. القيمة المعروضة للدالة هي نتيجة التعبير النهائي.

في المثال أعلاه، يحتوي stringLengthFunc على مرجع لدالة مجهولة تستخدم String كإدخال وتعرض طول المدخل String كإخراج من النوع Int. لهذا السبب، يتم الإشارة إلى نوع الدالة باسم (String) -> Int. ومع ذلك، لا يستدعي هذا الرمز الدالة. لاسترداد نتيجة الدالة، يجب عليك استدعاءها مثلما تفعل مع دالة مُسمّاة. يجب توفير String عند استدعاء stringLengthFunc، كما هو موضّح في المثال التالي:

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

val stringLength: Int = stringLengthFunc("Android")

الدوال ذات الترتيب الأعلى

يمكن للدالة أن تأخذ دالة أخرى كوسيطة. الدوال التي تستخدم دوال أخرى كوسيطات تسمى دوال ذات ترتيب أعلى. هذا النمط مفيد للتواصل بين المكونات بنفس الطريقة التي قد تستخدم بها واجهة استدعاء في Java.

فيما يلي مثال على دالة ذات ترتيب أعلى:

fun stringMapper(str: String, mapper: (String) -> Int): Int {
    // Invoke function
    return mapper(str)
}

تستخدم الدالة stringMapper() String مع دالة تستمد القيمة Int من String التي ترسلها إليها.

يمكنك استدعاء stringMapper() من خلال تمرير String ودالة تفي بمعلمة الإدخال الأخرى، أي الدالة التي تستخدم String كإدخال وتخرج Int، كما هو موضح في المثال التالي:

stringMapper("Android", { input ->
    input.length
})

إذا كانت الدالة المجهولة هي المعلمة last المحددة في دالة، يمكنك تمريرها خارج الأقواس المستخدمة لاستدعاء الدالة، كما هو موضح في المثال التالي:

stringMapper("Android") { input ->
    input.length
}

يمكن العثور على الدوال المجهولة في مكتبة Kotlin القياسية. لمزيد من المعلومات، راجع الدوال ذات الترتيب الأعلى وLambdas.

الفئات

جميع الأنواع المذكورة حتى الآن مدمجة في لغة البرمجة Kotlin. إذا كنت تريد إضافة نوع مخصّص خاص بك، يمكنك تحديد فئة باستخدام الكلمة الرئيسية class، كما هو موضّح في المثال التالي:

class Car

الخصائص

تمثل الفئات الحالة باستخدام الخصائص. الخاصية هي متغيّر على مستوى الفئة يمكن أن يتضمّن getter وsetter وحقل خلفي. بما أنّ قيادة السيارة تحتاج إلى عجلات، يمكنك إضافة قائمة من عناصر Wheel كسمة Car، كما هو موضّح في المثال التالي:

class Car {
    val wheels = listOf<Wheel>()
}

يُرجى العِلم أنّ wheels هو public val، ما يعني أنّه يمكن الوصول إلى wheels من خارج الصف Car ولا يمكن إعادة إسناده. إذا كنت تريد الحصول على مثيل لـ Car، يجب أولاً استدعاء الدالة الإنشائية له. من هناك، يمكنك الوصول إلى أي من خصائصها التي يمكن الوصول إليها.

val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car

إذا كنت ترغب في تخصيص العجلات، يمكنك تحديد دالة إنشاء مخصّصة تحدد كيفية تهيئة خصائص الفئة الخاصة بك:

class Car(val wheels: List<Wheel>)

في المثال أعلاه، تأخذ الدالة الإنشائية للفئة List<Wheel> كوسيطة إنشائية وتستخدم هذه الوسيطة لإعداد خاصية wheels.

دوال الفئة والتغليف

تستخدم الصفوف الوظائف لوضع نماذج للسلوك. يمكن للدوال تعديل الحالة، مما يساعدك على الكشف فقط عن البيانات التي تريد الكشف عنها. يعد التحكم في الوصول هذا جزءًا من مفهوم أكبر موجه للكائنات يُعرف باسم التغليف.

في المثال التالي، يتم الحفاظ على خصوصية السمة doorLock من أي عنصر خارج الفئة Car. لفتح قفل السيارة، يجب استدعاء الدالة unlockDoor() لإدخال مفتاح صالح، كما هو موضّح في المثال التالي:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

إذا كنت تريد تخصيص كيفية الإشارة إلى خاصية، يمكنك تقديم قيمة get وsetter مخصّصة. على سبيل المثال، إذا أردت عرض دالة getter لموقع إلكتروني أثناء حظر الوصول إلى دالة setter الخاصة بها، يمكنك تحديد قيمة setter هذه على أنّها private:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    var gallonsOfFuelInTank: Int = 15
        private set

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

باستخدام مزيج من الخصائص والدوال، يمكنك إنشاء فئات تصمم جميع أنواع الكائنات.

إمكانية التشغيل التفاعلي

من أهم ميزات لغة Kotlin هي إمكانية التشغيل التفاعلي السلسة مع لغة Java. وبما أنّ رمز Kotlin يتم تجميعه إلى رمز بايت JVM، يمكن للتعليمات البرمجية لـ Kotlin مباشرةً إلى رمز Java والعكس صحيح. وهذا يعني أنه يمكنك الاستفادة من مكتبات Java الحالية مباشرةً من Kotlin. علاوةً على ذلك، تتم كتابة معظم واجهات برمجة تطبيقات Android بلغة Java، ويمكنك الاتصال بها مباشرةً من Kotlin.

الخطوات التالية

Kotlin هي لغة مرنة وعملية ذات دعم وزخم متزايد. إلا أننا نشجعك على تجربته إذا لم تكن قد فعلت ذلك بعد. لمعرفة الخطوات التالية، يمكنك إلقاء نظرة على مستندات Kotlin الرسمية ودليل كيفية تطبيق أنماط لغة Kotlin الشائعة في تطبيقات Android.