extension function — آلية في Kotlin تسمح بإضافة طرق جديدة إلى الفئات الموجودة دون وراثة ودون تعديل الكود المصدري. تُصرَّح الدالة ببادئة على شكل نوع المستقبل وتُستدعى كطريقة عادية لذلك النوع. وفقًا Kotlin Documentation, 2025، تُجمَّع extension functions كطرق ثابتة مع المستقبل كمعامل أول، مما يضمن صفر overhead في وقت التشغيل مقارنة بالاستدعاءات العادية.
النقاط الرئيسية
extension function هي دالة تُصرَّح خارج الفئة ولكن تُستدعى كطريقة تابعة لها. تأخذ نوع مستقبل يُضاف إليه الطريقة الجديدة. داخل الدالة، تشير الكلمة المفتاحية this إلى مثيل نوع المستقبل. يتيح هذا الأسلوب تمديد الفئات النهائية وفئات المكتبات الخارجية وحتى الأنواع البدائية دون إنشاء أغلفة.
على عكس Java، حيث تتطلب إضافة طريقة إلى فئة موجودة إما الوراثة أو فئة أدوات ثابتة، يقدم Kotlin حلاً أنيقًا دون boilerplate. وفقًا لدراسة Kotlin Foundation (2024)، تعد extension functions من بين أكثر خمس ميزات استخدامًا في اللغة — 78% من مطوري Kotlin يستخدمونها في عملهم اليومي.
يترجم مُجمِّع Kotlin extension function إلى طريقة ثابتة حيث يُمرَّر مثيل المستقبل كمعامل أول. هذا يعني أن extension functions لا تُعدِّل bytecode للفئة الهدف ولا تخترق التغليف — فقط الحقول والطرق العامة للمستقبل تكون قابلة للوصول.
استخدم extension functions لكتابة دوال أدوات نظيفة مرتبطة منطقيًا بنوع معين ولكن لا يمكن إضافتها إلى كوده المصدر.
بناء الجملة الأساسي لـ extension function: اسم نوع المستقبل، نقطة، اسم الدالة، ثم المعاملات ونوع الإرجاع. داخل الدالة، المستقبل قابل للوصول عبر this.
// Extension function for String
fun String.isEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
// Called as regular String method
val result = "user@example.com".isEmail() // true
في هذا المثال، isEmail() يصبح متاحًا لجميع السلاسل النصية. داخل الدالة، this يشير إلى السلسلة النصية نفسها التي يُستدعى عليها الطريقة. يسمح Kotlin بحذف this في معظم الحالات — يمكنك كتابة contains("@") بدلاً من this.contains("@").
يمكن أن تقبل extension functions معاملات إضافية وتُرجِع قيمًا من أي نوع. هذا يجعلها دوال كاملة، وليس مجرد سكر نحوي.
fun List<Int>.defaultIfEmpty(default: Int): List<Int> {
if (this.isEmpty()) return listOf(default)
return this
}
val populated = listOf(1, 2).defaultIfEmpty(0)
val empty = listOf<Int>().defaultIfEmpty(0)
المعامل default يُحدد القيمة المرجعة لقائمة فارغة. يمكن أن تكون extension function عامة — النوع List
تستخدم extension functions في Kotlin التوزيع الثابت، وليس التوزيع الافتراضي. هذا فرق رئيسي عن طرق الفئات العادية. أي extension function سيتم استدعاؤها يُحدد في وقت الترجمة حسب النوع الثابت للمتغير، وليس حسب نوعه الفعلي في وقت التشغيل.
open class Animal
class Dog : Animal()
fun Animal.speak() = "Animal sound"
fun Dog.speak() = "Woof"
fun test() {
val animal: Animal = Dog()
println(animal.speak()) // "Animal sound" — static type Animal
val dog: Dog = Dog()
println(dog.speak()) // "Woof" — static type Dog
}
على الرغم من أن المتغير animal يشير إلى مثيل من Dog، إلا أنه يتم استدعاء extension function لـ Animal لأن النوع الثابت للمتغير هو Animal. لو كانت speak() طريقة افتراضية للفئة، لكان قد تم استدعاء Dog.speak(). هذا السلوك مهم في الاعتبار عند تصميم API باستخدام extension functions.
extension functions ليست أعضاء في الفئة — إنها دوال عادية تتطلب استيرادًا لاستخدامها في ملف آخر. يوفر Kotlin خيارين للاستيراد: باسم الدالة أو مع إعادة التسمية.
// Import by name
import com.example.extensions.isEmail
// Import with alias (to resolve conflicts)
import com.example.extensions.isEmail as isValidEmail
// Call after import
val valid = "test@test.com".isEmail()
إعادة التسمية عبر as مفيدة في حالات تضارب الأسماء — على سبيل المثال، إذا كانت مكتبتان توفران extension function بنفس الاسم لنفس النوع. في هذه الحالة، يمكنك استيراد إحداهما باسم مختلف واستدعائها باستخدام الاسم الجديد.
يمكن التصريح بـ extension functions على مستويات مختلفة:
| المستوى | الرؤية | مثال |
|---|---|---|
| Top-level | المشروع بأكمله بعد الاستيراد | fun String.isEmail() |
| Member extension | داخل الفئة المالكة | class A { fun B.ext() } |
| Local | داخل دالة | fun test() { fun String.ext() } |
دوال member extension هي حالة خاصة حيث تُصرَّح extension function داخل فئة أخرى. في هذه الحالة، يكون كل من المستقبل (this الخاص بالدالة) وأعضاء الفئة الخارجية قابلين للوصول داخل extension function.
إذا كانت الفئة تحتوي على طريقة بنفس التوقيع مثل extension function، فإن عضو الفئة يُستدعى دائمًا. لا تتجاوز extension function أبدًا طريقة الفئة — هذا قرار معماري لمنع التجاوزات العرضية.
class User {
fun greet() = "Hello from class"
}
fun User.greet() = "Hello from extension"
fun main() {
val user = User()
println(user.greet()) // "Hello from class"
}
حتى إذا تم تعريف extension function لاحقًا وبنفس التوقيع، سيختار المُجمِّع طريقة الفئة. يُصدر مُجمِّع Kotlin تحذيرًا عند اكتشاف هذه الحالة. الطريقة الوحيدة لاستدعاء extension function عند وجود طريقة فئة هي استدعاؤها كدالة عادية: greet(user).
في تطوير Android، أصبحت extension functions أداة قياسية للعمل مع View وContext وfragments. مكتبة Android KTX مبنية على extension functions، وتوفر أغلفة مناسبة لـ API الخاص بـ Android.
// Extension function for working with View
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
// Extension function for Context — toast
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// Extension function as DSL builder
fun String.colorFormat(color: String): String =
"$color$this\u001b[0m"
extension function toast() تجعل الاستدعاءات المعتمدة على Context موجزة: بدلاً من كتابة Toast.makeText(context, message, length).show()، يكفي كتابة context.toast("نص"). هذا يقلل من boilerplate ويجعل الكود أكثر قابلية للقراءة.
الأسئلة الشائعة
لا، تستخدم extension functions التوزيع الثابت. إذا أعلنت extension function لفئة أساسية ونفسها لفئة فرعية، أي منها سيتم استدعاؤها يُحدد حسب النوع الثابت للمتغير في وقت الترجمة، وليس حسب النوع الفعلي في وقت التشغيل.
يمكنك التصريح بـ extension functions لأنواع مستقبل قابلة للصفر: fun String?.isNullOrEmail(). داخل هذه الدالة، قد تكون this بقيمة null، لذلك يجب استخدام عوامل الاستدعاء الآمن أو الفحوصات الصريحة للصفر.
لا، تُجمَّع extension functions كطرق ثابتة. على مستوى bytecode، استدعاء extension function مطابق لاستدعاء طريقة ثابتة مع المستقبل كمعامل أول. لا يوجد انعكاس أو توزيع ديناميكي.
نعم، تعمل extension properties بشكل مشابه لـ extension functions ولكن لا يمكنها تخزين حالة — فقط getter و setter. على سبيل المثال: val List
في Java تكتب StringUtils.isEmail(str)، بينما في Kotlin تكتب str.isEmail(). الفرق ليس فقط في بناء الجملة: extension functions تدعم الإكمال التلقائي في IDE، وتحسن قابلية قراءة سلاسل الاستدعاءات، وتسمح لـ IDE باقتراح دوال ذات صلة لنوع معين. Java لا تمتلك هذه الإمكانيات.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا