هذا الدرس العملي حول الترميز هو جزء من دورة Kotlin التدريبية للمبرمجين. يمكنك تحقيق أقصى استفادة من هذه الدورة التدريبية إذا اتبعت ترتيب الخطوات في دروس البرمجة. استنادًا إلى معرفتك، قد تتمكّن من قراءة بعض الأقسام بسرعة. هذه الدورة التدريبية موجّهة للمبرمجين الذين يعرفون لغة برمجة تعتمد على العناصر ويريدون تعلُّم Kotlin.
مقدمة
في هذا الدرس العملي، ستتعرّف على عدد من الميزات المفيدة المختلفة في Kotlin، بما في ذلك الأزواج والمجموعات ودوال الإضافة.
بدلاً من إنشاء تطبيق نموذجي واحد، تم تصميم الدروس في هذه الدورة التدريبية لتعزيز معرفتك، ولكنها شبه مستقلة عن بعضها البعض حتى تتمكن من تصفّح الأقسام التي تعرفها. ولربطها ببعضها، تستخدم العديد من الأمثلة سمة حوض السمك. إذا أردت الاطّلاع على قصة حوض السمك بالكامل، يمكنك الرجوع إلى دورة Kotlin Bootcamp للمبرمجين على Udacity.
ما يجب معرفته
- بنية دوال وفئات وطُرق Kotlin
- كيفية استخدام REPL (حلقة القراءة والتقييم والطباعة) في Kotlin في IntelliJ IDEA
- كيفية إنشاء صف جديد في IntelliJ IDEA وتشغيل برنامج
أهداف الدورة التعليمية
- كيفية العمل مع أزواج وثلاثيات
- مزيد من المعلومات عن المجموعات
- تحديد الثوابت واستخدامها
- كتابة دوال الإضافة
الإجراءات التي ستنفذّها
- Learn about pairs, triples, and hash maps in the REPL
- التعرّف على طرق مختلفة لتنظيم الثوابت
- كتابة دالة إضافة وسمة إضافة
في هذه المهمة، ستتعرّف على الثنائيات والثلاثيات وكيفية تفكيكها. الأزواج والثلاثيات هي فئات بيانات مُعدّة مسبقًا لعنصرَين أو ثلاثة عناصر عامة. يمكن أن يكون ذلك مفيدًا، على سبيل المثال، في جعل الدالة تعرض أكثر من قيمة واحدة.
لنفترض أنّ لديك List
من السمك، ودالة isFreshWater()
للتحقّق مما إذا كان السمك من المياه العذبة أو المالحة. تعرض الدالة List.partition()
قائمتَين، إحداهما تتضمّن العناصر التي يكون فيها الشرط true
، والأخرى تتضمّن العناصر التي يكون فيها الشرط false
.
val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")
الخطوة 1: إنشاء بعض الأزواج والمجموعات الثلاثية
- افتح REPL (أدوات > Kotlin > Kotlin REPL).
- أنشئ زوجًا يربط قطعة من المعدات بالغرض الذي تُستخدَم من أجله، ثم اطبع القيم. يمكنك إنشاء زوج من خلال إنشاء تعبير يربط بين قيمتَين، مثل سلسلتَي نص، باستخدام الكلمة الرئيسية
to
، ثم استخدام.first
أو.second
للإشارة إلى كل قيمة.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
- أنشئ مجموعة ثلاثية واطبعها باستخدام
toString()
، ثم حوّلها إلى قائمة باستخدامtoList()
. يمكنك إنشاء ثلاثية باستخدامTriple()
مع 3 قيم. استخدِم.first
و.second
و.third
للإشارة إلى كل قيمة.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42) [6, 9, 42]
تستخدم الأمثلة أعلاه النوع نفسه لجميع أجزاء الزوج أو الثلاثي، ولكن هذا ليس شرطًا. يمكن أن تكون الأجزاء سلسلة أو رقمًا أو قائمة، على سبيل المثال، أو حتى زوجًا أو ثلاثة أضعاف.
- أنشئ زوجًا يكون الجزء الأول منه زوجًا أيضًا.
val equipment2 = ("fish net" to "catching fish") to "equipment"
println("${equipment2.first} is ${equipment2.second}\n")
println("${equipment2.first.second}")
⇒ (fish net, catching fish) is equipment ⇒ catching fish
الخطوة 2: تقسيم بعض الأزواج والمجموعات الثلاثية
يُطلق على تقسيم الأزواج والثلاثيات إلى أجزائها اسم التفكيك. عيِّن الزوج أو الثلاثي لعدد مناسب من المتغيرات، وستعيّن لغة Kotlin قيمة كل جزء بالترتيب.
- تفكيك زوج وطباعة القيم
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")
⇒ fish net is used for catching fish
- تفكيك ثلاثية وطباعة القيم
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42
يُرجى العِلم أنّ تفكيك الأزواج والثلاثيات يعمل بالطريقة نفسها كما هو الحال مع فئات البيانات، وقد تم تناول ذلك في درس برمجة سابق.
في هذه المهمة، ستتعرّف على المزيد من المعلومات حول المجموعات، بما في ذلك القوائم، ونوع جديد من المجموعات، وهو خرائط التجزئة.
الخطوة 1: التعرّف على مزيد من المعلومات عن القوائم
- تم تقديم القوائم والقوائم القابلة للتغيير في درس سابق. وهي بنية بيانات مفيدة جدًا، لذا توفّر Kotlin عددًا من الدوال المضمّنة للقوائم. راجِع هذه القائمة الجزئية للدوال الخاصة بالقوائم. يمكنك العثور على قوائم كاملة في مستندات Kotlin الخاصة بـ
List
وMutableList
.
الوظيفة | Purpose |
| أضِف عنصرًا إلى القائمة القابلة للتغيير. |
| إزالة عنصر من قائمة قابلة للتعديل |
| إرجاع نسخة من القائمة مع ترتيب العناصر بشكل عكسي |
| تعرِض |
| لعرض جزء من القائمة، بدءًا من الفهرس الأول وحتى الفهرس الثاني ولكن بدون تضمينه |
- أثناء العمل في REPL، أنشئ قائمة بالأرقام واستدعِ الدالة
sum()
عليها. يتم جمع كل العناصر.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
- أنشئ قائمة سلاسل واجمعها.
val list2 = listOf("a", "bbb", "cc")
println(list2.sum())
⇒ error: none of the following functions can be called with the arguments supplied:
- إذا لم يكن العنصر شيئًا تعرف
List
كيفية جمعه مباشرةً، مثل سلسلة، يمكنك تحديد كيفية جمعه باستخدام.sumBy()
مع دالة lambda، على سبيل المثال، للجمع حسب طول كل سلسلة. الاسم التلقائي لوسيطة lambda هوit
، ويشيرit
هنا إلى كل عنصر من عناصر القائمة أثناء التنقل فيها.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
- هناك المزيد الذي يمكنك عمله باستخدام القوائم. إحدى طرق الاطّلاع على الوظائف المتاحة هي إنشاء قائمة في IntelliJ IDEA، وإضافة النقطة، ثم الاطّلاع على قائمة الإكمال التلقائي في تلميح الأداة. يمكن استخدام هذه الطريقة مع أي كائن. جرِّبها مع قائمة.
- اختَر
listIterator()
من القائمة، ثم انتقِل إلى القائمة باستخدام عبارةfor
واطبع جميع العناصر مفصولة بمسافات.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
println("$s ")
}
⇒ a bbb cc
الخطوة 2: تجربة خرائط التجزئة
في Kotlin، يمكنك ربط أي شيء بأي شيء آخر باستخدام hashMapOf()
. خرائط التجزئة تشبه إلى حد ما قائمة من الأزواج، حيث تعمل القيمة الأولى كمفتاح.
- أنشئ خريطة تجزئة تطابق الأعراض، وهي المفاتيح، وأمراض الأسماك، وهي القيم.
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- يمكنك بعد ذلك استرداد قيمة المرض استنادًا إلى مفتاح الأعراض، باستخدام
get()
أو حتى الأقواس المربعة الأقصر[]
.
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
- حاوِل تحديد عَرَض غير مضمَّن في الخريطة.
println(cures["scale loss"])
⇒ null
إذا لم يكن المفتاح في الخريطة، ستؤدي محاولة عرض المرض المطابق إلى عرض null
. استنادًا إلى بيانات الخريطة، قد يكون من الشائع عدم العثور على تطابق لمفتاح محتمل. في مثل هذه الحالات، توفّر Kotlin الدالة getOrDefault()
.
- جرِّب البحث عن مفتاح ليس له تطابق باستخدام
getOrDefault()
.
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know
إذا كنت بحاجة إلى إجراء أكثر من مجرد عرض قيمة، يوفّر Kotlin الدالة getOrElse()
.
- غيِّر الرمز البرمجي لاستخدام
getOrElse()
بدلاً منgetOrDefault()
.
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this
بدلاً من عرض قيمة تلقائية بسيطة، يتم تنفيذ أي رمز برمجي بين الأقواس المعقوفة {}
. في المثال، تعرض الدالة else
سلسلة نصية فقط، ولكن يمكن أن تكون الدالة معقّدة مثل العثور على صفحة ويب تتضمّن علاجًا وعرضه.
تمامًا مثل mutableListOf
، يمكنك أيضًا إنشاء mutableMapOf
. تتيح لك الخريطة القابلة للتعديل إضافة عناصر وإزالتها. تعني كلمة "قابل للتغيير" إمكانية التغيير، بينما تعني كلمة "غير قابل للتغيير" عدم إمكانية التغيير.
- إنشاء خريطة مستودع يمكن تعديلها، وربط سلسلة معدات بعدد العناصر أنشئها باستخدام شبكة صيد السمك، ثم أضِف 3 أدوات تنظيف أحواض السمك إلى المستودع باستخدام
put()
، وأزِل شبكة صيد السمك باستخدامremove()
.
val inventory = mutableMapOf("fish net" to 1)
inventory.put("tank scrubber", 3)
println(inventory.toString())
inventory.remove("fish net")
println(inventory.toString())
⇒ {fish net=1, tank scrubber=3}{tank scrubber=3}
في هذه المهمة، ستتعرّف على الثوابت في Kotlin وطرق تنظيمها المختلفة.
الخطوة 1: التعرّف على الفرق بين const وval
- في REPL، جرِّب إنشاء ثابت رقمي. في Kotlin، يمكنك إنشاء ثوابت على مستوى أعلى وتعيين قيمة لها في وقت الترجمة باستخدام
const val
.
const val rocks = 3
يتم تحديد القيمة ولا يمكن تغييرها، وهو ما يشبه إلى حد كبير تعريف val
عادية. إذًا ما الفرق بين اشتراكَي const val
وval
؟ يتم تحديد قيمة const val
في وقت الترجمة البرمجية، بينما يتم تحديد قيمة val
أثناء تنفيذ البرنامج، ما يعني أنّه يمكن تعيين val
بواسطة دالة في وقت التشغيل.
وهذا يعني أنّه يمكن تعيين قيمة لـ val
من دالة، ولكن لا يمكن تعيين قيمة لـ const val
.
val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok
بالإضافة إلى ذلك، لا يعمل const val
إلا على مستوى أعلى، وفي فئات singleton التي تم تعريفها باستخدام object
، وليس مع الفئات العادية. يمكنك استخدام ذلك لإنشاء ملف أو عنصر فردي يحتوي على ثوابت فقط، واستيرادها حسب الحاجة.
object Constants {
const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2
الخطوة 2: إنشاء عنصر مصاحب
لا تتضمّن لغة Kotlin مفهوم الثوابت على مستوى الفئة.
لتحديد الثوابت داخل فئة، عليك تضمينها في عناصر مصاحبة تم تعريفها باستخدام الكلمة الرئيسية companion
. الكائن المرافق هو في الأساس كائن فردي ضمن الفئة.
- أنشئ فئة تحتوي على عنصر ثابت من النوع String.
class MyClass {
companion object {
const val CONSTANT3 = "constant in companion"
}
}
الفرق الأساسي بين الكائنات المصاحبة والكائنات العادية هو:
- يتم تهيئة الكائنات المصاحبة من الدالة الإنشائية الثابتة للفئة الحاوية، أي يتم إنشاؤها عند إنشاء الكائن.
- يتم تهيئة الكائنات العادية بشكل غير مباشر عند الوصول إلى هذا الكائن لأول مرة، أي عند استخدامه لأول مرة.
هناك المزيد، ولكن كل ما تحتاج إلى معرفته الآن هو تضمين الثوابت في الفئات في عنصر مصاحب.
في هذه المهمة، ستتعرّف على كيفية توسيع نطاق سلوك الفئات. من الشائع جدًا كتابة دوال مساعدة لتوسيع سلوك فئة معيّنة. توفّر لغة Kotlin بنية ملائمة لتعريف دوال الأدوات المساعدة هذه، وهي دوال الإضافة.
تتيح لك دوال الإضافة إضافة دوال إلى فئة حالية بدون الحاجة إلى الوصول إلى الرمز المصدر الخاص بها. على سبيل المثال، يمكنك تعريفها في ملف Extensions.kt الذي يشكّل جزءًا من الحزمة. لا يؤدي ذلك إلى تعديل الفئة فعليًا، ولكنه يتيح لك استخدام صيغة النقطة عند استدعاء الدالة على عناصر تلك الفئة.
الخطوة 1: كتابة دالة إضافة
- أثناء العمل في REPL، اكتب دالة إضافة بسيطة،
hasSpaces()
، للتحقّق مما إذا كانت السلسلة تحتوي على مسافات. يتم إضافة بادئة إلى اسم الدالة تتضمّن الفئة التي تعمل عليها. داخل الدالة، يشيرthis
إلى العنصر الذي يتم استدعاؤه، ويشيرit
إلى المكرّر في استدعاءfind()
.
fun String.hasSpaces(): Boolean {
val found = this.find { it == ' ' }
return found != null
}
println("Does it have spaces?".hasSpaces())
⇒ true
- يمكنك تبسيط الدالة
hasSpaces()
. لا حاجة إلىthis
بشكلٍ صريح، ويمكن اختصار الدالة إلى تعبير واحد وعرضه، وبالتالي لا حاجة إلى الأقواس المعقوفة{}
حوله أيضًا.
fun String.hasSpaces() = find { it == ' ' } != null
الخطوة 2: التعرّف على قيود الإضافات
لا يمكن لوظائف الإضافة الوصول إلا إلى واجهة برمجة التطبيقات العامة للفئة التي يتم توسيعها. لا يمكن الوصول إلى المتغيّرات التي تكون private
.
- جرِّب إضافة دوال ملحقة إلى سمة تحمل العلامة
private
.
class AquariumPlant(val color: String, private val size: Int)
fun AquariumPlant.isRed() = color == "red" // OK
fun AquariumPlant.isBig() = size > 50 // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
- افحص الرمز أدناه وحاوِل معرفة ما سيتم طباعته.
open class AquariumPlant(val color: String, private val size: Int)
class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)
fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")
val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print() // what will it print?
⇒ GreenLeafyPlant AquariumPlant
plant.print()
صور مطبوعة GreenLeafyPlant
قد تتوقّع أن تطبع aquariumPlant.print()
القيمة GreenLeafyPlant
أيضًا، لأنّه تم تعيين القيمة plant
لها. ولكن يتم تحديد النوع في وقت الترجمة البرمجية، لذا ستتم طباعة AquariumPlant
.
الخطوة 3: إضافة سمة إضافة
بالإضافة إلى دوال الإضافة، تتيح لك Kotlin أيضًا إضافة خصائص الإضافة. كما هو الحال مع دوال الإضافة، عليك تحديد الفئة التي تريد توسيعها، ثم إضافة نقطة، ثم اسم السمة.
- أثناء العمل في REPL، أضِف سمة إضافة
isGreen
إلىAquariumPlant
، والتي تكونtrue
إذا كان اللون أخضر.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
يمكن الوصول إلى السمة isGreen
تمامًا مثل أي سمة عادية. وعند الوصول إليها، يتم استدعاء الدالة getter الخاصة بالسمة isGreen
للحصول على القيمة.
- اطبع السمة
isGreen
للمتغيّرaquariumPlant
ولاحظ النتيجة.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
الخطوة 4: التعرّف على المتلقّيات التي تقبل القيم الخالية
يُطلق على الصف الذي يتم توسيعه اسم المستلِم، ويمكن جعل هذا الصف يقبل القيم الخالية. في حال إجراء ذلك، يمكن أن يكون المتغيّر this
المستخدَم في النص null
، لذا احرص على إجراء اختبار لذلك. عليك استخدام متلقّي يقبل القيم الخالية إذا كنت تتوقّع أنّ المتصلين يريدون استدعاء طريقة الإضافة على متغيرات تقبل القيم الخالية، أو إذا كنت تريد توفير سلوك تلقائي عند تطبيق الدالة على null
.
- لا يزال العمل في REPL، حدِّد طريقة
pull()
تأخذ مستقبِلاً قابلاً للتصغير. يتم الإشارة إلى ذلك بعلامة استفهام?
بعد النوع وقبل النقطة. داخل النص، يمكنك اختبار ما إذا كانthis
ليسnull
باستخدام علامة استفهام-نقطة-تطبيق?.apply.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- في هذه الحالة، لن يظهر أي ناتج عند تشغيل البرنامج. بما أنّ
plant
هيnull
، لا يتم استدعاءprintln()
الداخلية.
تتسم دوال الإضافة بفعاليتها العالية، ويتم تنفيذ معظم مكتبة Kotlin العادية كدوال إضافة.
في هذا الدرس، تعرّفت على المزيد من المعلومات حول المجموعات والثوابت، كما تعرّفت على قوة دوال وخصائص الإضافة.
- يمكن استخدام الأزواج والثلاثيات لعرض أكثر من قيمة واحدة من دالة. على سبيل المثال:
val twoLists = fish.partition { isFreshWater(it) }
- تتضمّن لغة Kotlin العديد من الدوال المفيدة لإجراء
List
، مثلreversed()
وcontains()
وsubList()
. - يمكن استخدام
HashMap
لربط المفاتيح بالقيم. على سبيل المثال:val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- يمكنك تعريف الثوابت في وقت الترجمة باستخدام الكلمة الأساسية
const
. يمكنك وضعها في المستوى الأعلى أو تنظيمها في عنصر فردي أو وضعها في عنصر مصاحب. - الكائن المرافق هو كائن فردي ضمن تعريف الفئة، ويتم تعريفه باستخدام الكلمة الرئيسية
companion
. - يمكن أن تضيف دوال وسمات الإضافة وظائف إلى فئة. على سبيل المثال:
fun String.hasSpaces() = find { it == ' ' } != null
- يتيح لك العنصر القابل للتصغير إنشاء إضافات على فئة يمكن أن تكون
null
. يمكن إقران عامل التشغيل?.
بعامل التشغيلapply
للتحقّق منnull
قبل تنفيذ الرمز. على سبيل المثال:this?.apply { println("removing $this") }
مستندات Kotlin
إذا أردت الحصول على مزيد من المعلومات حول أي موضوع في هذه الدورة التدريبية، أو إذا واجهتك أي مشكلة، يمكنك الانتقال إلى https://kotlinlang.org.
برامج تعليمية حول Kotlin
يتضمّن الموقع الإلكتروني https://try.kotlinlang.org برامج تعليمية غنية بصريًا تُعرف باسم Kotlin Koans، ومترجم مستند إلى الويب، ومجموعة كاملة من المستندات المرجعية مع أمثلة.
دورة Udacity التدريبية
للاطّلاع على دورة Udacity التدريبية حول هذا الموضوع، يُرجى الانتقال إلى برنامج Kotlin التدريبي للمبرمجين.
IntelliJ IDEA
يمكنك العثور على مستندات IntelliJ IDEA على موقع JetBrains الإلكتروني.
يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:
- حدِّد واجبًا منزليًا إذا لزم الأمر.
- توضيح كيفية إرسال الواجبات المنزلية للطلاب
- وضع درجات للواجبات المنزلية
يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.
إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.
الإجابة عن هذه الأسئلة
السؤال 1
أيّ من الخيارات التالية يعرض نسخة من قائمة؟
▢ add()
▢ remove()
▢ reversed()
▢ contains()
السؤال 2
أيّ من دوال الإضافة التالية في class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean)
سيؤدي إلى ظهور خطأ في المترجم؟
▢ fun AquariumPlant.isRed() = color == "red"
▢ fun AquariumPlant.isBig() = size > 45
▢ fun AquariumPlant.isExpensive() = cost > 10.00
▢ fun AquariumPlant.isNotLeafy() = leafy == false
السؤال 3
أيّ مما يلي ليس مكانًا يمكنك فيه تحديد الثوابت باستخدام const val
؟
▢ في المستوى الأعلى من الملف
▢ في الصفوف العادية
▢ في عناصر أحادية
▢ في الكائنات المصاحبة
انتقِل إلى الدرس التالي:
للحصول على نظرة عامة على الدورة التدريبية، بما في ذلك روابط تؤدي إلى دروس تطبيقية أخرى حول الترميز، يُرجى الاطّلاع على "برنامج Kotlin التدريبي للمبرمجين: مرحبًا بك في الدورة التدريبية".