الأصناف العامة (Generics) في Kotlin: لماذا هي ضرورية وكيفية استخدامها
عند التطوير بلغة Kotlin، غالبًا ما تواجه الحاجة لكتابة كود يمكنه العمل مع أنواع مختلفة من البيانات. بدلاً من إنشاء دوال أو فئات منفصلة لكل من Int و String و Double وهكذا، يمكنك استخدام آلية الأصناف العامة (Generics). تشرح هذه المقالة ما هي الأصناف العامة في Kotlin، ولماذا هي ضرورية وكيفية تطبيقها عمليًا.
ما هي الأصناف العامة ولماذا هي ضرورية؟
الأصناف العامة (Generics) هي أداة تسمح بكتابة فئات وواجهات ودوال تعمل مع أنواع بيانات عشوائية. تخيل أنك تقوم بإنشاء صندوق يمكنك وضع أي شيء فيه: كتاب، هاتف، أو طعام. بدلاً من إنشاء صندوق منفصل لكل عنصر، تقوم بإنشاء صندوق واحد متعدد الاستخدامات.
المزايا الرئيسية لاستخدام الأصناف العامة:
- أمان النوع (Type Safety) — يتحقق المترجم من الأنواع في مرحلة الترجمة، مما يمنع أخطاء وقت التشغيل.
- إعادة استخدام الكود — الكود المكتوب مرة واحدة يعمل مع أي أنواع.
- سهولة القراءة — يصبح الكود أكثر وضوحًا، حيث يتم تحديد الأنواع التي يعمل معها بشكل صريح.
بدون الأصناف العامة، سيتعين عليك استخدام Any والقيام بتحويل الأنواع باستمرار، مما يعرضك للأخطاء. مع الأصناف العامة، يقوم المترجم بنفسه بمراقبة صحة الأنواع.
أساسيات الأصناف العامة في Kotlin: معاملات النوع
في Kotlin، يتم تنفيذ الأصناف العامة من خلال معاملات النوع. يتم تحديدها بين قوسين زاويتين <>. غالبًا ما تُستخدم الأحرف T (نوع)، E (عنصر)، K (مفتاح)، V (قيمة)، ولكن يمكن استخدام أي أسماء.
الدوال العامة (Generic Functions)
لنبدأ بمثال بسيط — دالة تُرجع الوسيط الذي تم تمريره إليها:
fun <T> identity(value: T): T { return value}
fun main() { val number: Int = identity(42) val text: String = identity("مرحبًا") println(number) // 42 println(text) // مرحبًا}هنا، <T> قبل اسم الدالة يُعلن عن معامل النوع. يتم استنتاج النوع T تلقائيًا من الوسيط، لذلك لا حاجة لتحديده صراحةً.
الفئات العامة (Generic Classes)
يمكن أيضًا أن تكون الفئات عامة. لننشئ حاوية بسيطة:
class Box<T>(private val item: T) { fun getItem(): T { return item }}
fun main() { val stringBox = Box("كتاب") val intBox = Box(123)
println(stringBox.getItem()) // كتاب println(intBox.getItem()) // 123}يمكن للفئة Box<T> تخزين كائن من أي نوع. عند إنشاء مثيل، نحدد النوع المحدد، ويضمن المترجم أن getItem() سيعيد هذا النوع بالضبط.
قيود الأنواع (Type Bounds)
أحيانًا تحتاج إلى تقييد الأنواع التي يمكن استخدامها في الأصناف العامة. على سبيل المثال، لجعل الدالة تعمل فقط مع الأرقام. لهذا، تُستخدم القيود (bounds) مع الكلمة المفتاحية :.
الحد الأعلى (Upper Bound)
fun <T : Number> sum(a: T, b: T): Double { return a.toDouble() + b.toDouble()}
fun main() { println(sum(10, 20)) // 30.0 println(sum(3.5, 2.5)) // 6.0 // println(sum("A", "B")) // خطأ في الترجمة}هنا، T : Number يعني أن النوع T يجب أن يكون وريثًا للفئة Number. Int و Double و Float مناسبة، بينما String ليست كذلك.
قيود متعددة
إذا كنت بحاجة إلى عدة قيود، استخدم where:
interface Printable { fun print()}
fun <T>