التحميل الزائد للمعاملات (Operator Overloading) في لغة Kotlin
تُتيح لغة Kotlin إجراءَ مجموعةٍ مُعرَّفة مسبقًا من العمليات على أنواع البيانات المختلفة وذلك باستخدام رموزٍ ثابتةٍ تعتمدها لغة Kotlin مثل الرمز + أو الرمز * وتختلف فيما بينها بالأولويّة (precedence)، حيث توجد دالةٌ (إما دالة من الصنف [member] أو دالة إضافيّة [extension]) باسمٍ ثابتٍ لكلّ معاملٍ مُعرَّف بحسب النوع (نوعٌ يساريٌّ للعمليات الثنائيّة [binary operations] ونوع متحولاتٍ [argument type] للعمليات الأحاديّة [unary operations])، ويجب تحديد الدوال التي تحتوي على تحميلٍ زائدٍ للمعاملات بالمُحدَّد operator، تناقش الصفحة الاصطلاحات (conventions) التي تنظِّم التحميل الزائد لمختلف المعاملات.
العمليات الأحادية (Unary Operations)
المعاملات الأحادية البادئة (Unary Prefix Operators)
| التعبير | يُترجَم إلى |
|---|---|
a+
|
a.unaryPlus()
|
a-
|
a.unaryMinus()
|
a!
|
a.not()
|
يقوم المُترجِم (compiler) بالخطوات الآتية عند إجراء إحدى العمليات السابقة ولتكن a+ :
- تحديد نوع المتحول
aوليكنT - البحث عن الدالة
unaryPlus() المسبوقة بالمُحدِّدoperatorوالتي لا تحتوي أيّة متحولات للمستقبِلT(إما دالة من الصنف [member] أو دالة إضافيّة [extension]) - إن لم تكن الدالة موجودةً أو كانت غامضةً فسينتُج خطأٌ بالترجمة (compilation error)
- إذا وُجدت الدالة وكان نوعها المُعاد
Rفسيكون للتعبيرa+النوعRأيضًا
وتُتاح هذه العمليات (بالإضافة لعملياتٍ أخرى) للأنواع الأساسيّة في لغة Kotlin ولا تتطلَّب استدعاء الدوال، فيمكن مثلًا استخدام التحميل الزائد للمعامل الأحاديّ - بالشكل:
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
println(-point) // "(-10, -20)" سيظهر
معاملات الزيادة والنقصان
| التعبير | يُترجَم إلى |
|---|---|
++a
|
a.inc()
|
--a
|
a.dec()
|
إذ ستُسنَد القيمة التي تعيدها الدالة inc() أو dec() إلى المتحول الذي يُلحَق به المعامل ++ أو -- دون أن تغيّر الدالةُ الكائنَ الذي استُدعيت من خلاله. يقوم المترجم بالخطوات الآتية لتنفيذ العملية اللاحقة (postfix) ولتكن ++a مثلًا:
- تحديد نوع المتحول
aوليكن النوعT
- البحث عن الدالة
inc() والمُعرَّفة بالمُحدِّدoperatorوتكون بدون متحولاتٍ وقابلةً للتطبيق على النوعT - التحقُّق من أنّ النوع المُعاد من هذه الدالة هو نوعٌ فرعيٌّ للنوع
T
وينتُج عن حساب التعبير:
- تخزين القيمة الابتدائيّة (initial value) للمتحول
aفي متحول مؤقتa0 - إسناد قيمة نتيجة الدالة
inc()إلى المتحول السابقa - إعادة المتحول
a0كنتيجةٍ للتعبير
وتماثل خطوات تنفيذ العملية --a الخطوات السابقة.
أمّا معاملات الزيادة والنقصان بصيغتها البادئة (prefix) بالشكل: a++ و a-- فهي تعمل بالطريقة السابقة ذاتها وتكون النتيجة:
- إسناد نتيجة الدالة
a.inc() إلى المتحولa - إعادة القيمة الجديدة للمتحول
aكنتيجةٍ للتعبير
العمليات الثنائيّة (Binary Operations)
المعاملات الحسابيّة (Arithmetic Operators)
| التعبير | يُترجم إلى |
|---|---|
a + b
|
a.plus(b)
|
a - b
|
a.minus(b)
|
a * b
|
a.times(b)
|
a / b
|
a.div(b)
|
a % b
|
a.rem(b) و a.mod(b) (مُستبعدة)
|
a..b
|
a.rangeTo(b)
|
تدعم Kotlin المعامل rem بدءًا من الإصدار Kotlin 1.1 ليحل محلّ mod في الإصدار Kotlin 1.0 (مُستبعد في الإصدار الحاليّ).
مثال
يبدأ الصنف Counter الآتي بقيمةٍ ابتدائيّةٍ من الممكن زيادتها باستخدام التحميل الزائد للمعامل + بالشكل:
data class Counter(val dayIndex: Int) {
operator fun plus(increment: Int): Counter {
return Counter(dayIndex + increment)
}
}
المعامل in
| التعبير | يُترجَم إلى |
|---|---|
a in b
|
b.contains(a)
|
a !in b
|
!b.contains(a)
|
وتكون الإجراءات نفسها في المعاملين حيث يُعكَس ترتيب المتحولات فقط.
معاملات الوصول المفهرس (Indexed Access Operators)
| التعبير | يُترجَم إلى |
|---|---|
a[i]
|
a.get(i)
|
a[i,j]
|
a.get(i, j)
|
a[i_1, ..., i_n]
|
(a.get(i_1, ..., i_n
|
a[i] = b
|
a.set(i, b)
|
a[i, j] = b
|
a.set(i, j, b)
|
a[i_1, ..., i_n] = b
|
a.set(i_1, ..., i_n, b)
|
إذ تُترجَم الأقواس [] إلى استدعاء الدالة get أو set بما يتناسب مع عدد المتحولات.
معاملات الاستدعاء (Invoke Operators)
| التعبير | يُترجَم إلى |
|---|---|
a()
|
a.invoke()
|
a(i)
|
a.invoke(i)
|
a(i,j)
|
a.invoke(i,j)
|
a(i_1, ..., i_n)
|
a.invoke(i_1, ..., i_n)
|
إذ تُترجَم الأقواس () إلى استدعاءات للدالة invoke بالعدد المناسب من المتحولات.
عمليات الإسناد المُحسَّنة (Augmented Assignments)
| التعبير | يُترجَم إلى |
|---|---|
a += b
|
a.plusِAssign(b)
|
a -= b
|
a.minusAssign(b)
|
a *= b
|
a.timesAssign(b)
|
a /= b
|
a.divAssign(b)
|
a %= b
|
a.remAssign(b) و a.modAssign(b) (مُستبعدة)
|
إذ يقوم المُترجِم (compiler) بالخطوات الآتية لعمليات الإسناد، ولتكن العملية a += b مثلًا:
- إن كانت الدالة متاحةً:
- إن كانت الدالة الثنائيّة الموافقة لها (مثل الدالة
plus()بالنسبة للدالةplusAssign()) متاحةً أيضًا فيجب الإعلام بخطأ الالتباس (ambiguity) - التأكّد من أنّ النوع المُعاد هو
Unitوالإعلام بالخطأ خلاف ذلك - توليد الشيفرة المناسبة للدالة
a.plusِAssign(b)
- إن كانت الدالة الثنائيّة الموافقة لها (مثل الدالة
- إن لم تكن الدالة متاحةً: يحاول توليد الشيفرة
a = a + bوهذا يتضمَّن التحقُّق من النوع؛ أي أن يكون نوع التعبيرa + bنوعًا فرعيًا (subtype) منa
ولا تُعدُّ عمليات الإسنادا تعابيرًا (expressions) في Kotlin.
معاملات المساواة (Equality) والاختلاف (Inequality)
| التعبير | يُترجَم إلى |
|---|---|
a == b
|
a?.equals(b) ?: (b === null)
|
a != b
|
!(a?.equals(b) ?: (b === null))
|
ويكون معاملا التحقُّق من المرجعيّة (=== و ==!) غير قابلين للتحميل الزائد (not overloadable) وبالتالي ما من استخدامٍ اصطلاحيٍّ لهما.
ويتميز المعامل == بأنه يُترجَم إلى تعبيرٍ معقَّدٍ يُضمِر القيمة null أي تكون نتيجة العملية null == null صحيحةً دائمًا، وتكون قيمة x == null خاطئةً دائمًا إن كان المتحول x متحولًا nullable (أي لا يقبل القيمة الفارغة null) ولن تُستدعَى بذلك الدالة x.equals().
معاملات المقارنة (Comparison Operators)
| التعبير | يُترجم إلى |
|---|---|
a > b
|
a.compareTo(b) > 0
|
a < b
|
a.compareTo(b) < 0
|
a >= b
|
a.compareTo(b) >= 0
|
a <= b
|
a.compareTo(b) <= 0
|
إذ تُترجَم كافّة عمليات المقارنة إلى استدعاءاتٍ للدالة compareTo التي تعيد النوع Int.
معاملات تعميم الخاصّيّات (Property Delegation Operators)
وهي الدوال provideDelegate و getValue و setValue ولمزيدٍ من المعلومات راجع الخاصّيّات المُعمَّمة (delegated properties).
استدعاء الدوال المسماة بالصيغة الضمنيّة (Infix)
تمكن محاكاة العمليّات بالصيغة الضمنيّة (infix) بالاعتماد على استدعاءات الدوال الضمنيّة.