أصناف البيانات (Data Classes) في لغة Kotlin

من موسوعة حسوب
اذهب إلى: تصفح، ابحث
تُنشَأ بعض الأصناف بهدف تخزين البيانات فيها بشكلٍ أساسيّ، وبالتالي فإنّ كلّ ما تقوم به هذه الأصناف من وظائف يرتبط بالبيانات، وهذا ما يُسمى بأصناف البيانات في لغة Kotlin وتُعرَّف بالمُحدِّد data كما يلي:
data class User(val name: String, val age: Int)

قواعد عامّة

يقوم المُترجِم في الأصناف من هذه النوع باشتقاق العناصر (deriving members) الآتية من كلِّ الخاصّيّات المُعرَّفة في الباني الأساسيّ (primary constructor):

  • كلًا من equals()‎ و hashCode()‎
  • الدالة toString()‎ بشكلها ‎"User(name=John, age=42‎)‎"
  • الدوال بالصيغة componentN() functions بما يتناسب مع الخاصّيّات وترتيبها أثناء التصريح
  • الدالة copy()

ويجب أن تُحقِّق أصناف البيانات الشروط الآتية لضمان الترابط والسلوك الفعّال في الشيفرة المُولَّدة:

  • أن يحتوي الباني الأساسيّ (primary constructor) على معامل (parameter) واحدٍ على الأقل
  • أن تكون متغيِّراته إمّا من النوع var أو النوع val
  • ألّا يكون الصنف من النوع abstract أو open أو sealed أو inner
  • يمكن للصنف إعادة تعريف استخدام (implement) الواجهات (interfaces) فقط (هذا قبل الإصدار 1.1)

وبما يخصُّ وراثة العناصر (members inheritance) فيجب التقيُّد بالقواعد الآتية:

  • إذا وُجِد تعريف استخدام (implementation) الدوال equals()‎ أو hashCode()‎ أو toString()‎ في صنف البيانات أو إعادة تعريف استخدامٍ من النوع final في الصنف الأعلى (superclass) فإنّ هذه الدوال لا تُولَّد (generated) بل تُستخدم تعريفاتها الموجودة.
  • إذا احتوى النوع الأعلى (supertype) على دوال componentN()‎ المفتوحة (من النوع open) والتي تعيد أنواعًا متوافقة، حينها تُولَّد الدوال المُوافقة في صنف البيانات ويُعاد تعريف (override) تلك الدوال في النوع الأعلى، وإذا لم يكن من الممكن إعادة تعريفها بسبب كونها final أو عدم التوافق في ترويستها (signature) فسينتُج خطأ عن ذلك.
  • يُحدُّ في Kotlin 1.2 من اشتقاق صنف البيانات من نوعٍ يحتوي على دالة copy(...)‎ وبتأشيرةٍ متوافقةٍ (matching signature) وسيُمنع ذلك بدءًا من الإصدار Kotlin 1.3.
  • لا يُسمح بوجود تعريف الاستخدام (implementation) الصريح لأيّ من الدالتين componentN()‎ و copy()‎.

ويُسمَح بدءًا من الإصدار 1.1 أن تكون أصناف البيانات إضافةً (extend) للأصناف الأخرى. (راجع الأصناف المغلقة [sealed classes] للحصول على الأمثلة)

وإذا تطلَّب الصنف المُولَّد في JVM أن يحتوي على بانٍ بدون معاملات (parameterless) فيجب حينئذٍ تحديد القيم الافتراضيّة لكافّة الخاصّيّات كما في الشيفرة: (راجع البواني [constructors])
data class User(val name: String = "", val age: Int = 0)

الخاصيات (Properties) المُعرَّفة في بنية الصنف (Class Body)

يَستخدِم المُترجِم الخاصّيّات المُعرَّفة داخل الباني الأساسيّ (primary constructor) للدوال المولَّدة تلقائيًا فقط، وبالتالي فإنه لاستبعاد أيّ خاصّيّة من تعاريف الاستخدام (implementations) المُولَّدة، يجب تعريفها في بُنية الصنف، مثل:
data class Person(val name: String) {
    var age: Int = 0
}
ففي الشيفرة السابقة يُسمَح باستخدام الخاصية name فقط في أيّ من الدوال equals()‎ و hashCode()‎ و toString()‎ و copy()‎ وسيكون هناك دالة واحد للعناصر (component function) باسم component1()‎ ، وعلى الرغم من أنّه يمكن أن يكون لكائنين من النوع Person أعمارٌ مختلفةٌ فإنها ستُعامَل وكأنها متساوية، جرِّب الشيفرة الآتية:
val person1 = Person("John")
val person2 = Person("John")

person1.age = 10
person2.age = 20
إذ يُعدُّ الشرط: person1 == person2 بقيمة true.

دالة النسخ copy()‎

قد تحتاج في حالاتٍ كثيرة إلى استنساخ كائنٍ ما (object) والتعديل في "بعض" خاصّيّاته (properties) مع المحافظة على الأخرى كما هي بدون تعديل، وهذا ما تقوم به الدالة copy()‎، ففي الصنف السابق User سيكون تعريف استخدام (implementation) الدالة كما يلي:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
وهذا سيُتيح الاستخدام الآتي في الشيفرة:
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

أصناف البيانات وتفكيك التصريحات (Destructuring Declarations)

تُولَّد دوال العناصر (component functions) بهدف استخدامها في تفكيك التصريحات في أصناف البيانات، كما في الشيفرة الآتية:
val jane = User("Jane", 35) 
val (name, age) = jane
println("$name, $age years of age") // ستظهر العبارة "Jane, 35 years of age"

أصناف البيانات القياسيّة (Standard Data Classes)

توفّر المكتبة القياسيّة الصنفين Pair و Triple لأنّ استخدام أصناف البيانات المُسماة يكون -في معظم الأحيان- خيارًا أفضل، لأنّها تجعل الشيفرة أسهل قراءةً عبر تزويدها بأسماءٍ معبِّرةٍ للخاصّيّات.

مصادر