مقدمة إلى Generics في Kotlin
Generics (الأدوية) هي إحدى الميزات الرئيسية للغة Kotlin، والتي تتيح لك كتابة كود مرن، آمن من حيث الأنواع، وقابل لإعادة الاستخدام. إذا سبق لك كتابة List<String> أو Map<Int, User>، فقد استخدمت الأدوية بالفعل، حتى دون أن تدرك ذلك. ولكن ما الذي تخفيه هذه الأقواس الزاوية وكيف تستخرج أقصى فائدة منها؟ في هذه المقالة، سنشرح لماذا نحتاج إلى Generics في Kotlin، وكيفية تطبيقها عمليًا، وما هي الميزات المتقدمة (variance, reified, where) التي ستساعدك على كتابة كود أكثر نقاءً وأمانًا.
هذه المقالة موجهة للمطورين الذين هم على دراية بأساسيات Kotlin ويرغبون في تعميق معرفتهم. سنغطي كل شيء: من بناء الجملة الأساسي إلى أمثلة حقيقية من كود الإنتاج.
لماذا نحتاج Generics؟ المشكلة بدونها
تخيل أنك بحاجة لكتابة دالة تأخذ قائمة من أي عناصر وتعيد العنصر الأول. بدون الأدوية، ستكتب هكذا:
fun getFirst(list: List<Any>): Any { return list[0]}الآن، إذا مررت List<String>، فستحصل على Any كمخرج. للتعامل معه كسلسلة نصية، سيتعين عليك إجراء تحويل نوع صريح:
val first = getFirst(listOf("Kotlin", "Java")) as Stringfirst.length // خطير! قد يسبب ClassCastExceptionهذا غير مريح وغير آمن. سيظهر الخطأ فقط في وقت التشغيل. تحل Generics هذه المشكلة عن طريق الحفاظ على معلومات النوع في مرحلة التجميع.
المزايا الرئيسية لـ Generics:
- أمان الأنواع — يتحقق المترجم من الأنواع في مرحلة التجميع، وليس في وقت التشغيل.
- إزالة التحويلات الصريحة — يصبح الكود أنظف وأكثر قابلية للقراءة.
- قابلية إعادة الاستخدام — تعمل فئة أو دالة واحدة مع أنواع مختلفة.
بناء الجملة الأساسي: الفئات، الدوال، والقيود
الفئات العامة (Generic Classes)
أبسط طريقة للبدء هي تعريف فئة بمعامل نوع واحد أو أكثر. تُحدد معاملات النوع بين قوسين زاوية <T> (يمكن أن يكون الحرف أي شيء، لكن تقليديًا يُستخدم T، E، K، V):
class Box<T>(val value: T)
fun main() { val stringBox = Box("Kotlin") // يتم استنتاج النوع تلقائيًا val intBox = Box(42) // Box<Int>
println(stringBox.value.length) // يعمل بدون تحويل! println(intBox.value + 1) // 43}يمكن لـ Kotlin استنتاج النوع، لذلك ليس من الضروري كتابة Box<String> — يكفي Box("Kotlin").
الدوال العامة (Generic Functions)
يمكن أن تكون الدوال عامة أيضًا. يُحدد معامل النوع قبل اسم الدالة:
fun <T> getMiddleElement(list: List<T>): T? { if (list.isEmpty()) return null return list[list.size / 2]}
fun main() { val numbers = listOf(1, 2, 3, 4, 5) val middle = getMiddleElement(numbers) // يتم استنتاج النوع Int? تلقائيًا println(middle) // 3}القيود (where / upper bound)
أحيانًا تحتاج إلى تقييد الأنواع التي يمكن استخدامها في الأدوية. على سبيل المثال، لاستدعاء الأسلوب .compareTo()، يجب أن ينفذ النوع الواجهة Comparable:
fun <T : Comparable<T>> maxOfTwo(a: T, b: T): T { return if (a >= b) a else b}
fun main() { println(maxOfTwo(10, 20)) // 20 println(maxOfTwo("A", "B")) // B // maxOfTwo(10, "A") // خطأ في التجميع: الأنواع غير متطابقة}إذا كنت بحاجة إلى عدة قيود، استخدم الكلمة المفتاحية where:
interface Printable { fun print()}
fun <T> process(item: T) where T : Comparable<T>, T : Printable
```