‫Kotlin Bootcamp for Programmers 5.2: الأنواع العامة

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

مقدمة

في هذا الدرس العملي، ستتعرّف على الفئات والدوال والطرق العامة وكيفية عملها في Kotlin.

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

ما يجب معرفته

  • بنية دوال وفئات وطُرق Kotlin
  • كيفية إنشاء صف جديد في IntelliJ IDEA وتشغيل برنامج

أهداف الدورة التعليمية

  • كيفية استخدام الفئات والطُرق والدوال العامة

الإجراءات التي ستنفذّها

  • إنشاء فئة عامة وإضافة قيود
  • إنشاء النوعَين in وout
  • إنشاء دوال وطُرق ودوال إضافية عامة

مقدمة حول الأنواع العامة

تحتوي لغة Kotlin، مثل العديد من لغات البرمجة، على أنواع عامة. يتيح لك النوع العام إنشاء فئة عامة، وبالتالي جعل الفئة أكثر مرونة.

لنفترض أنّك بصدد تنفيذ فئة MyList تحتوي على قائمة بعناصر. بدون الأنواع العامة، ستحتاج إلى تنفيذ إصدار جديد من MyList لكل نوع: إصدار لـ Double، وإصدار لـ String، وإصدار لـ Fish. باستخدام الأنواع العامة، يمكنك جعل القائمة عامة، وبالتالي يمكنها الاحتفاظ بأي نوع من العناصر. وهو يشبه جعل النوع حرف بدل يمكن أن يتناسب مع العديد من الأنواع.

لتحديد نوع عام، ضَع T بين قوسين زاويين <T> بعد اسم الفئة. (يمكنك استخدام حرف آخر أو اسم أطول، ولكنّ الاصطلاح الخاص بالنوع العام هو T).

class MyList<T> {
    fun get(pos: Int): T {
        TODO("implement")
    }
    fun addItem(item: T) {}
}

يمكنك الإشارة إلى T كما لو كان نوعًا عاديًا. نوع القيمة المعروضة للدالة get() هو T، ونوع المَعلمة للدالة addItem() هو T. بالطبع، القوائم العامة مفيدة جدًا، لذا تم إنشاء الفئة List في Kotlin.

الخطوة 1: إنشاء تسلسل هرمي للأنواع

في هذه الخطوة، ستنشئ بعض الفئات لاستخدامها في الخطوة التالية. تم تناول موضوع الفئات الفرعية في درس تطبيقي سابق حول الترميز، ولكن إليك مراجعة موجزة.

  1. لإبقاء المثال بسيطًا، أنشئ حزمة جديدة ضمن src وسمِّها generics.
  2. في حزمة generics، أنشئ ملف Aquarium.kt جديدًا. يتيح لك ذلك إعادة تحديد العناصر باستخدام الأسماء نفسها بدون حدوث تعارضات، لذا سيتم وضع بقية الرمز البرمجي لهذا الدرس العملي في هذا الملف.
  3. أنشئ تسلسلاً هرميًا لأنواع إمدادات المياه. ابدأ بإنشاء WaterSupply فئة open، حتى يمكن إنشاء فئات فرعية منها.
  4. أضِف مَعلمة منطقية var، needsProcessing. يؤدي ذلك تلقائيًا إلى إنشاء خاصية قابلة للتغيير، بالإضافة إلى دالة جلب ودالة ضبط.
  5. أنشئ فئة فرعية TapWater توسّع WaterSupply، وأدخِل true بدلاً من needsProcessing، لأنّ مياه الصنبور تحتوي على مواد مضافة تضرّ بالأسماك.
  6. في TapWater، عرِّف دالة باسم addChemicalCleaners() تضبط قيمة needsProcessing على false بعد تنظيف المياه. يمكن ضبط السمة needsProcessing من TapWater، لأنّها public تلقائيًا ويمكن الوصول إليها من الفئات الفرعية. في ما يلي الرمز البرمجي المكتمل.
package generics

open class WaterSupply(var needsProcessing: Boolean)

class TapWater : WaterSupply(true) {
   fun addChemicalCleaners() {
       needsProcessing = false
   }
}
  1. أنشئ فئتَين فرعيتَين إضافيتَين من WaterSupply، اسميهما FishStoreWater وLakeWater. لا تحتاج السمة FishStoreWater إلى معالجة، ولكن يجب فلترة السمة LakeWater باستخدام الطريقة filter(). بعد الفلترة، لن تحتاج إلى معالجتها مرة أخرى، لذا في filter()، اضبط needsProcessing = false.
class FishStoreWater : WaterSupply(false)

class LakeWater : WaterSupply(true) {
   fun filter() {
       needsProcessing = false
   }
}

إذا كنت بحاجة إلى معلومات إضافية، راجِع الدرس السابق حول الميراث في Kotlin.

الخطوة 2: إنشاء فئة عامة

في هذه الخطوة، ستعدّل الفئة Aquarium لتتوافق مع أنواع مختلفة من إمدادات المياه.

  1. في ملف Aquarium.kt، حدِّد فئة Aquarium، مع وضع <T> بين قوسين بعد اسم الفئة.
  2. أضِف سمة غير قابلة للتغيير waterSupply من النوع T إلى Aquarium.
class Aquarium<T>(val waterSupply: T)
  1. اكتب دالة باسم genericsExample(). هذا ليس جزءًا من فئة، لذا يمكن وضعه في المستوى الأعلى من الملف، مثل الدالة main() أو تعريفات الفئات. في الدالة، أنشئ Aquarium ومرِّر إليه WaterSupply. بما أنّ المَعلمة waterSupply عامة، يجب تحديد النوع بين قوسين زاويين <>.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
}
  1. في genericsExample()، يمكن لرمزك الوصول إلى waterSupply في حوض السمك. بما أنّ نوعه هو TapWater، يمكنك استدعاء addChemicalCleaners() بدون أي عمليات تحويل للأنواع.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. عند إنشاء العنصر Aquarium، يمكنك إزالة الأقواس الزاوية والمحتوى بينها لأنّ Kotlin يتضمّن ميزة استنتاج النوع. لذلك، ليس هناك سبب لاستخدام TapWater مرتين عند إنشاء المثيل. يمكن استنتاج النوع من خلال الوسيط إلى Aquarium، وسيظل ينشئ Aquarium من النوع TapWater.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. لمعرفة ما يحدث، اطبع needsProcessing قبل وبعد استدعاء addChemicalCleaners(). في ما يلي الدالة المكتملة.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
    aquarium.waterSupply.addChemicalCleaners()
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
}
  1. أضِف الدالة main() لاستدعاء genericsExample()، ثم شغِّل برنامجك ولاحظ النتيجة.
fun main() {
    genericsExample()
}
⇒ water needs processing: true
water needs processing: false

الخطوة 3: تحديد نطاق البحث بشكل أكبر

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

  1. في genericsExample()، أنشئ Aquarium، مع تمرير سلسلة لـ waterSupply، ثم اطبع السمة waterSupply الخاصة بحوض السمك.
fun genericsExample() {
    val aquarium2 = Aquarium("string")
    println(aquarium2.waterSupply)
}
  1. شغِّل برنامجك ولاحظ النتيجة.
⇒ string

والنتيجة هي السلسلة التي تم تمريرها، لأنّ Aquarium لا يفرض أي قيود على T.يمكن تمرير أي نوع، بما في ذلك String.

  1. في genericsExample()، أنشئ Aquarium آخر، مع تمرير null إلى waterSupply. إذا كانت قيمة waterSupply فارغة، اطبع "waterSupply is null".
fun genericsExample() {
    val aquarium3 = Aquarium(null)
    if (aquarium3.waterSupply == null) {
        println("waterSupply is null")
    }
}
  1. نفِّذ برنامجك ولاحظ النتيجة.
⇒ waterSupply is null

لماذا يمكنني اجتياز null عند إنشاء Aquarium؟ هذا ممكن لأنّ T يمثّل تلقائيًا النوع Any? القابل للتصغير، وهو النوع في أعلى التسلسل الهرمي للأنواع. ما يلي يعادل ما كتبته سابقًا.

class Aquarium<T: Any?>(val waterSupply: T)
  1. لعدم السماح بتمرير null، اجعل T من النوع Any بشكل صريح، وذلك عن طريق إزالة ? بعد Any.
class Aquarium<T: Any>(val waterSupply: T)

في هذا السياق، يُطلق على Any اسم قيد عام. وهذا يعني أنّه يمكن تمرير أي نوع من أجل T طالما أنّه ليس null.

  1. ما تريده حقًا هو التأكّد من أنّه لا يمكن تمرير سوى WaterSupply (أو إحدى فئاته الفرعية) إلى T. استبدِل Any بـ WaterSupply لتحديد قيد عام أكثر تحديدًا.
class Aquarium<T: WaterSupply>(val waterSupply: T)

الخطوة 4: إضافة المزيد من عمليات التحقّق

في هذه الخطوة، ستتعرّف على الدالة check() للمساعدة في ضمان عمل الرمز البرمجي على النحو المتوقّع. الدالة check() هي دالة مكتبة عادية في Kotlin. تعمل هذه الدالة كتأكيد وستعرض IllegalStateException إذا تم تقييم وسيطتها على أنّها false.

  1. أضِف طريقة addWater() إلى فئة Aquarium لإضافة الماء، مع check() يضمن عدم الحاجة إلى معالجة الماء أولاً.
class Aquarium<T: WaterSupply>(val waterSupply: T) {
    fun addWater() {
        check(!waterSupply.needsProcessing) { "water supply needs processing first" }
        println("adding water from $waterSupply")
    }    
}

في هذه الحالة، إذا كانت قيمة needsProcessing صحيحة، سيؤدي check() إلى حدوث استثناء.

  1. في genericsExample()، أضِف رمزًا لإنشاء Aquarium باستخدام LakeWater، ثم أضِف بعض الماء إليه.
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.addWater()
}
  1. شغِّل برنامجك، وسيظهر لك استثناء لأنّ الماء يحتاج إلى فلترة أولاً.
⇒ Exception in thread "main" java.lang.IllegalStateException: water supply needs processing first
        at Aquarium.generics.Aquarium.addWater(Aquarium.kt:21)
  1. أضِف خطوة لفلترة المياه قبل إضافتها إلى Aquarium. الآن، عند تشغيل برنامجك، لن يتم عرض أي استثناء.
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.waterSupply.filter()
    aquarium4.addWater()
}
⇒ adding water from generics.LakeWater@880ec60

تتناول المعلومات الواردة أعلاه أساسيات الأنواع العامة. تغطّي المهام التالية المزيد، ولكن المفهوم المهم هو كيفية تعريف واستخدام فئة عامة مع قيد عام.

في هذه المهمة، ستتعرّف على أنواع in وout مع الأنواع العامة. النوع in هو نوع يمكن تمريره إلى فئة فقط، وليس إرجاعه. النوع out هو نوع لا يمكن إرجاعه إلا من فئة.

إذا نظرت إلى الفئة Aquarium، ستلاحظ أنّه لا يتم عرض النوع العام إلا عند الحصول على السمة waterSupply. لا تتوفّر أي طرق تأخذ قيمة من النوع T كمَعلمة (باستثناء تحديدها في الدالة الإنشائية). تتيح لك لغة Kotlin تحديد out أنواع لهذا الغرض تحديدًا، ويمكنها استنتاج معلومات إضافية حول الأماكن التي يمكن فيها استخدام الأنواع بأمان. وبالمثل، يمكنك تحديد أنواع in للأنواع العامة التي يتم تمريرها فقط إلى الطرق، وليس إرجاعها. يسمح ذلك للغة Kotlin بإجراء عمليات فحص إضافية لضمان أمان الرمز البرمجي.

النوعان in وout هما توجيهات لنظام أنواع Kotlin. إنّ شرح نظام الأنواع بالكامل يخرج عن نطاق هذا البرنامج التدريبي (فهو معقّد إلى حدّ ما)، ومع ذلك، سيضع المترجم علامة على الأنواع التي لم يتم وضع العلامة in وout عليها بشكل مناسب، لذا عليك التعرّف عليها.

الخطوة 1: تحديد نوع "خارج"

  1. في فئة Aquarium، غيِّر T: WaterSupply إلى النوع out.
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    ...
}
  1. في الملف نفسه، خارج الفئة، عرِّف الدالة addItemTo() التي تتوقّع Aquarium من WaterSupply.
fun addItemTo(aquarium: Aquarium<WaterSupply>) = println("item added")
  1. اتّصِل بـ addItemTo() من genericsExample() وشغِّل برنامجك.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    addItemTo(aquarium)
}
⇒ item added

يمكن أن تضمن لغة Kotlin أنّ addItemTo() لن تنفّذ أي إجراء غير آمن من حيث النوع مع النوع العام WaterSupply، لأنّه تم تعريفه على أنّه نوع out.

  1. إذا أزلت الكلمة الرئيسية out، سيُظهر المحول البرمجي خطأ عند استدعاء addItemTo()، لأنّ Kotlin لا يمكنها التأكّد من أنّك لا تفعل أي شيء غير آمن مع النوع.

الخطوة 2: تحديد نوع in

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

  1. في ملف Aquarium.kt، حدِّد واجهة Cleaner تأخذ نوعًا عامًا T مقيّدًا بنوع WaterSupply. بما أنّه يتم استخدامه فقط كوسيطة للدالة clean()، يمكنك تحويله إلى مَعلمة in.
interface Cleaner<in T: WaterSupply> {
    fun clean(waterSupply: T)
}
  1. لاستخدام واجهة Cleaner، أنشئ فئة TapWaterCleaner تنفّذ Cleaner للتنظيف TapWater عن طريق إضافة مواد كيميائية.
class TapWaterCleaner : Cleaner<TapWater> {
    override fun clean(waterSupply: TapWater) =   waterSupply.addChemicalCleaners()
}
  1. في الفئة Aquarium، عدِّل addWater() لتلقّي Cleaner من النوع T، ونظِّف الماء قبل إضافته.
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    fun addWater(cleaner: Cleaner<T>) {
        if (waterSupply.needsProcessing) {
            cleaner.clean(waterSupply)
        }
        println("water added")
    }
}
  1. عدِّل مثال الرمز genericsExample() لإنشاء TapWaterCleaner وAquarium مع TapWater، ثم أضِف بعض الماء باستخدام المنظّف. وسيستخدم المنظّف حسب الحاجة.
fun genericsExample() {
    val cleaner = TapWaterCleaner()
    val aquarium = Aquarium(TapWater())
    aquarium.addWater(cleaner)
}

ستستخدم Kotlin معلومات النوع in وout للتأكّد من أنّ الرمز البرمجي يستخدم الأنواع العامة بأمان. من السهل تذكُّر Out وin: يمكن تمرير أنواع out إلى الخارج كقيم معروضة، ويمكن تمرير أنواع in إلى الداخل كوسيطات.

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

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

الخطوة 1: إنشاء دالة عامة

  1. في generics/Aquarium.kt، أنشئ دالة isWaterClean() تأخذ Aquarium. عليك تحديد النوع العام للمَعلمة، وأحد الخيارات هو استخدام WaterSupply.
fun isWaterClean(aquarium: Aquarium<WaterSupply>) {
   println("aquarium water is clean: ${aquarium.waterSupply.needsProcessing}")
}

ولكن هذا يعني أنّه يجب أن يحتوي Aquarium على مَعلمة من النوع out ليتم استدعاؤها. في بعض الأحيان، تكون السمتان out أو in مقيّدة جدًا لأنّك تحتاج إلى استخدام نوع لكلّ من الإدخال والإخراج. يمكنك إزالة شرط out من خلال جعل الدالة عامة.

  1. لجعل الدالة عامة، ضَع أقواسًا مثلثة بعد الكلمة الرئيسية fun مع نوع عام T وأي قيود، وفي هذه الحالة WaterSupply. غيِّر Aquarium ليتم تقييده بواسطة T بدلاً من WaterSupply.
fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) {
   println("aquarium water is clean: ${!aquarium.waterSupply.needsProcessing}")
}

T هي مَعلمة نوع isWaterClean() يتم استخدامها لتحديد النوع العام لحوض السمك. هذا النمط شائع جدًا، لذا من المستحسن أن تأخذ بعض الوقت لتجربته.

  1. استدعِ الدالة isWaterClean() عن طريق تحديد النوع بين قوسَين زاويَين بعد اسم الدالة مباشرةً وقبل الأقواس.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean<TapWater>(aquarium)
}
  1. بسبب استنتاج النوع من الوسيط aquarium، لا يلزم تحديد النوع، لذا عليك إزالته. شغِّل برنامجك وراقِب الناتج.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean(aquarium)
}
⇒ aquarium water is clean: false

الخطوة 2: إنشاء طريقة عامة بنوع مُجسَّد

يمكنك استخدام دوال عامة للطرق أيضًا، حتى في الفئات التي لها نوع عام خاص بها. في هذه الخطوة، ستضيف طريقة عامة إلى Aquarium تتحقّق مما إذا كان لها نوع WaterSupply.

  1. في الفئة Aquarium، عرِّف الدالة hasWaterSupplyOfType() التي تأخذ المَعلمة العامة R (يتم استخدام T حاليًا) التي تقتصر على WaterSupply، وتعرض true إذا كان waterSupply من النوع R. هذا يشبه الدالة التي حدّدتها سابقًا، ولكن داخل الفئة Aquarium.
fun <R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R
  1. لاحظ أنّ R الأخيرة يظهر تحتها خط أحمر. مرِّر مؤشر الماوس فوقه لمعرفة الخطأ.
  2. لإجراء فحص is، عليك إخبار Kotlin بأنّ النوع reified أو حقيقي ويمكن استخدامه في الدالة. لإجراء ذلك، ضَع inline قبل الكلمة الرئيسية fun، وreified قبل النوع العام R.
inline fun <reified R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R

بعد إعادة تحديد نوع، يمكنك استخدامه كنوع عادي، لأنّه يصبح نوعًا حقيقيًا بعد التضمين. وهذا يعني أنّه يمكنك إجراء عمليات التحقّق من النوع is.

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

  1. استخدِم TapWater كنوع. كما هو الحال مع استدعاء الدوال العامة، يمكنك استدعاء الطرق العامة باستخدام أقواس الزاوية مع النوع بعد اسم الدالة. نفِّذ برنامجك ولاحظ النتيجة.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())   // true
}
⇒ true

الخطوة 3: إنشاء دوال الإضافة

يمكنك أيضًا استخدام الأنواع المحدّدة للدوال العادية ودوال الإضافة.

  1. خارج الفئة Aquarium، عرِّف دالة إضافية في WaterSupply باسم isOfType() تتحقّق مما إذا كان WaterSupply الذي تم تمريره من نوع معيّن، مثل TapWater.
inline fun <reified T: WaterSupply> WaterSupply.isOfType() = this is T
  1. استدعِ دالة الإضافة تمامًا مثل الطريقة.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.waterSupply.isOfType<TapWater>())  
}
⇒ true

باستخدام وظائف الإضافة هذه، لا يهمّ نوع Aquarium (Aquarium أو TowerTank أو أي فئة فرعية أخرى)، طالما أنّها Aquarium. يُعدّ استخدام بنية عرض النجوم طريقة ملائمة لتحديد مجموعة متنوعة من التطابقات. وعند استخدام عرض النجمة، سيحرص Kotlin أيضًا على عدم اتخاذ أي إجراء غير آمن.

  1. لاستخدام عرض النجوم، ضَع <*> بعد Aquarium. نقل hasWaterSupplyOfType() لتصبح دالة إضافية، لأنّها ليست جزءًا من واجهة برمجة التطبيقات الأساسية في Aquarium.
inline fun <reified R: WaterSupply> Aquarium<*>.hasWaterSupplyOfType() = waterSupply is R
  1. غيِّر المكالمة إلى hasWaterSupplyOfType() ونفِّذ برنامجك.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())
}
⇒ true

في المثال السابق، كان عليك وضع علامة reified على النوع العام وجعل الدالة inline، لأنّ Kotlin تحتاج إلى معرفة ذلك في وقت التشغيل، وليس في وقت الترجمة فقط.

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

اتضح أنّ المترجم البرمجي يمكنه إنشاء رمز برمجي صحيح بدون الاحتفاظ بالأنواع العامة حتى وقت التشغيل. ولكن هذا يعني أنّك قد تنفّذ أحيانًا إجراءً، مثل is، على أنواع عامة لا يمكن للمترجم البرمجي التعامل معه. لهذا السبب، أضافت Kotlin أنواعًا مجسَّدة أو حقيقية.

يمكنك الاطّلاع على مزيد من المعلومات حول الأنواع المحدّدة وإزالة الأنواع في مستندات Kotlin.

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

  • أنشئ فئات عامة لجعل الرمز أكثر مرونة.
  • أضِف قيودًا عامة للحدّ من الأنواع المستخدَمة مع الأنواع العامة.
  • استخدِم النوعَين in وout مع الأنواع العامة لتوفير عملية أفضل للتحقّق من الأنواع من أجل حصر الأنواع التي يتم تمريرها إلى الفئات أو إرجاعها منها.
  • إنشاء دوال وطرق عامة للعمل مع الأنواع العامة على سبيل المثال:
    fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) { ... }
  • استخدِم دوال الإضافة العامة لإضافة وظائف غير أساسية إلى فئة.
  • تكون الأنواع المحدّدة ضرورية أحيانًا بسبب محو الأنواع. تستمر الأنواع المحدّدة، على عكس الأنواع العامة، حتى وقت التشغيل.
  • استخدِم الدالة check() للتأكّد من أنّ الرمز البرمجي يعمل على النحو المتوقّع. على سبيل المثال:
    check(!waterSupply.needsProcessing) { "water supply needs processing first" }

مستندات Kotlin

إذا أردت الحصول على مزيد من المعلومات حول أي موضوع في هذه الدورة التدريبية، أو إذا واجهتك أي مشكلة، يمكنك الانتقال إلى https://kotlinlang.org.

برامج تعليمية حول Kotlin

يتضمّن الموقع الإلكتروني https://try.kotlinlang.org برامج تعليمية غنية بصريًا تُعرف باسم Kotlin Koans، ومترجم مستند إلى الويب، ومجموعة كاملة من المستندات المرجعية مع أمثلة.

دورة Udacity التدريبية

للاطّلاع على دورة Udacity التدريبية حول هذا الموضوع، يُرجى الانتقال إلى برنامج Kotlin التدريبي للمبرمجين.

IntelliJ IDEA

يمكنك العثور على مستندات IntelliJ IDEA على موقع JetBrains الإلكتروني.

يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:

  • حدِّد واجبًا منزليًا إذا لزم الأمر.
  • توضيح كيفية إرسال الواجبات المنزلية للطلاب
  • وضع درجات للواجبات المنزلية

يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.

إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.

الإجابة عن هذه الأسئلة

السؤال 1

أي مما يلي هو اصطلاح تسمية نوع عام؟

<Gen>

<Generic>

<T>

<X>

السؤال 2

يُطلق على القيود المفروضة على الأنواع المسموح بها لنوع عام ما يلي:

▢ قيود عامة

‫▢ قيد عام

‫▢ توضيح المعلومات

▢ حدّ نوع عام

السؤال 3

تعني إعادة التجسيد ما يلي:

▢ تم احتساب تأثير التنفيذ الفعلي لأحد العناصر.

‫▢ تم ضبط فهرس إدخال مقيّد على الصف.

▢ تم تحويل مَعلمة النوع العام إلى نوع حقيقي.

▢ تم تشغيل مؤشر خطأ عن بُعد.

انتقِل إلى الدرس التالي: 6. التلاعب الوظيفي

للحصول على نظرة عامة على الدورة التدريبية، بما في ذلك روابط تؤدي إلى دروس تطبيقية أخرى حول الترميز، يُرجى الاطّلاع على "برنامج Kotlin التدريبي للمبرمجين: مرحبًا بك في الدورة التدريبية".