الدالة المضمنة في Kotlin: ما هي، بناء الجملة والاستخدام

المؤلف: IT Sectr نُشر: 2026-06-21 وقت القراءة: 9 دق

الدالة المضمنة — آلية في Kotlin يتم فيها استبدال جسم الدالة مباشرة في كل موقع استدعاء في وقت الترجمة. هذا يزيل الحمل الزائد الناتج عن إنشاء فئات مجهولة وكائنات لمعاملات الامدا. وفقًا لـ Kotlin Documentation, 2025، فإن الكلمة المفتاحية inline فعالة بشكل خاص للدوال عالية الترتيب، حيث كل لامدا بدون تضمين تنشئ كائن FunctionN منفصل، مما يثقل كاهل جامع القمامة.

النقاط الرئيسية

  • الدالة المضمنة — دالة يتم تضمين جسمها في موقع الاستدعاء في وقت الترجمة
  • تقليل الحمل الزائد — يلغي إنشاء فئات مجهولة وكائنات للامدا
  • الإرجاع غير المحلي — يمكن للامدا داخل الدوال المضمنة تنفيذ return من الدالة الخارجية
  • noinline — يمنع تضمين معاملات لامدا محددة
  • crossinline — يسمح بالإرجاع غير المحلي ولكن يمنع return من سياق الدالة المضمنة

ما هي الدالة المضمنة في Kotlin؟

الدالة المضمنة هي دالة موسومة بالكلمة المفتاحية inline. لا ينشئ مترجم Kotlin كود بايت منفصل مع استدعاء لها — بل ينسخ جسم الدالة مباشرة في كل موقع استدعاء. الهدف الرئيسي هو تحسين الدوال عالية الترتيب التي تقبل تعبيرات لامدا، حيث أن كل لامدا في الوضع الطبيعي تنشئ كائن فئة Function مجهول.

وفقًا لـ JetBrains Tech Blog (2024)، يمكن أن يؤدي استخدام الدوال المضمنة في Kotlin إلى تقليل عدد الكائنات المنشأة بنسبة 40–60% في الدوال التي تستخدم الامدا بشكل مكثف. في الحلقات والعمليات عالية التحميل (الترتيب، تصفية المجموعات) يوفر هذا تحسينًا ملموسًا في الأداء.

بدون inline، يتم ترجمة كل لامدا إلى فئة مجهولة (أو مثيل لواجهة وظيفية مركبة). بالنسبة للامدا التي تلتقط المتغيرات، يتم إنشاء كائنات غلاف إضافية. تزيل الدوال المضمنة كل هذه الكائنات في وقت الترجمة، وتستبدلها بكود مباشر يصل إلى المتغيرات المحلية بدون أغلفة.

استخدم inline فقط للدوال ذات معاملات لامدا — مترجم Kotlin نفسه يحذر إذا كان inline لا يوفر فائدة.

بناء جملة الدالة المضمنة وكيفية عملها

ما عليك سوى إضافة الكلمة المفتاحية inline قبل إعلان الدالة. يقوم المترجم تلقائيًا باستبدال جسم الدالة في مواقع الاستدعاء. تستمر الدالة نفسها في الوجود في كود البايت للحالات التي لا يتم استدعاؤها فيها مباشرة (على سبيل المثال، من كود Java).

kotlin
inline fun Int.repeatAction(action: (Int) -> Unit) {
    for (i in 0 until this) {
        action(i)
    }
}

// استدعاء — يتم تضمين كود الامدا في جسم الدالة
5.repeatAction { index ->
    println("Index: $index")
}

بعد الترجمة، سيكون الكود أعلاه مكافئًا لـ:

kotlin
// ماذا يحدث بعد التضمين (بشكل تخطيطي):
val $this = 5
for (i in 0 until $this) {
    println("Index: $i")
}

لا يتم إنشاء كائنات للامدا — يتم تنفيذ كود الإجراء مباشرة. هذا هو جوهر التحسين: بدلاً من استدعاء Function.invoke() — إدراج مباشر للكود مع جسم الامدا.

التحقق من خلال التفكيك

للتحقق من التضمين، افتح Tools > Kotlin > Show Kotlin Bytecode في IntelliJ IDEA وانقر فوق Decompile. سترى أنه بدلاً من استدعاء repeatAction مع لامدا، يتم إنشاء إدراج مباشر لجسم الدالة مع حلقة for.

مشكلة الحمل الزائد للامدا وحلها

يتم ترجمة كل لامدا في Kotlin إلى أحد ثلاثة أشكال. الأول — إذا كانت الامدا لا تلتقط متغيرات، فإنها تصبح طريقة ثابتة للفئة التي تم الإعلان عنها فيها. الثاني — إذا التقطت متغيرًا واحدًا، يتم إنشاء فئة مجهولة. الثالث — إذا التقطت عدة متغيرات، يتم إنشاء فئة مجهولة مع حقول لكل متغير ملتقط.

نوع الامدابدون inlineمع inline
بدون التقاططريقة ثابتة واحدة (معاد استخدامها)تضمين كامل، بدون استدعاء
مع التقاط متغير واحدفئة مجهولة (كائن واحد)تضمين كامل، بدون كائن
مع التقاط N متغيراتفئة مجهولة مع N حقولتضمين كامل، بدون كائن
متكررةاستدعاء عاديinline محظور

وفقًا لـ Android Performance Patterns (Google, 2024)، في التطبيقات ذات الاستخدام المكثف للمجموعات (التصفية، الترتيب، التجميع) تقلل الدوال المضمنة من التخصيصات بنسبة 25–35%. يكون التأثير ملحوظًا بشكل خاص في Jetpack Compose، حيث يؤدي كل تغيير حالة إلى إعادة تركيب مع العديد من الامدا.

الإرجاع غير المحلي والقيود

لا يمكن للامدا في دالة عادية إجراء return من الدالة الخارجية — فقط return محلي من الامدا نفسها (عبر return@label). في دالة مضمنة، يتم تضمين الامدا في جسم الدالة المستدعية، لذا يصبح الإرجاع غير المحلي ممكنًا: return داخل الامدا ينهي الدالة الخارجية.

kotlin
inline fun findFirst(
    items: List<Int>,
    predicate: (Int) -> Boolean
): Int {
    for (item in items) {
        if (predicate(item)) {
            return item
        }
    }
    return -1
}

fun processNumbers() {
    val numbers = listOf(1, 2, 3)
    val firstEven = findFirst(numbers) { it % 2 == 0 }
    // return في الامدا سيعيد null من processNumbers()
}

الإرجاع غير المحلي مناسب للإنهاء المبكر، ولكنه قد يؤدي إلى أخطاء. إذا تم استخدام الامدا في سياق غير محلي (مخزنة في متغير)، فسيؤدي الإرجاع غير المحلي إلى RuntimeException. يصدر مترجم Kotlin تحذيرًا عند محاولة هذا التخزين.

noinline و crossinline: التحكم في التضمين

عندما تحتوي الدالة على عدة معاملات لامدا، قد يكون من الضروري تضمين بعضها فقط. لهذا يُستخدم noinline — فهو يمنع تضمين معامل لامدا معين، ويتركه ككائن Function عادي.

معدّل crossinline يحل المشكلة المعاكسة: يتم تضمين الامدا، ولكن يُحظر الإرجاع غير المحلي. هذا ضروري عند استخدام الامدا داخل لامدا أخرى أو في سياق لا يُسمح فيه بـ return (على سبيل المثال، تمريرها إلى Runnable).

kotlin
inline fun processWithCallback(
    data: String,
    crossinline onSuccess: (String) -> Unit,
    noinline onError: (Exception) -> Unit
) {
    try {
        val result = process(data)
        onSuccess(result)
    } catch (e: Exception) {
        onError(e)
    }
}

// noinline: يمكن تخزين onError في متغير أو تمريره إلى مكان آخر
val errorHandler = { e: Exception -> log(e.message) }
processWithCallback("input", { println(it) }, errorHandler)

في المثال، onSuccess موسومة كـ crossinline — سيتم تضمينها، ولكن لا يمكن استخدام return داخلها. onError موسومة كـ noinline — لا يتم تضمينها، لذا يمكن تمريرها ككائن، أو تخزينها في حقل فئة، أو استخدامها كمستمع.

قيود الدوال المضمنة والتوصيات

للدوال المضمنة قيود. الدوال المضمنة المتكررة محظورة — سيرجع المترجم خطأً. لا يمكن أن تتمتع الدوال المضمنة برؤية private أو internal إذا تم الإعلان عنها في وحدة أخرى، ولكن هذا قيد رؤية، لا علاقة له بآلية التضمين نفسها.

يزداد حجم كود البايت مع كل استدعاء لدالة مضمنة، حيث يتم نسخ الجسم. وفقًا لـ Kotlin Coding Conventions (JetBrains, 2025)، يُوصى باستخدام inline فقط للدوال حتى 10–15 سطرًا. بالنسبة للدوال الكبيرة، قد يتم إلغاء فائدة تضمين الامدا بسبب زيادة حجم APK (أمر بالغ الأهمية في Android بسبب حد 64K أسلوب).

kotlin
// الممارسة الموصى بها
inline fun withLock(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
        return action()
    } finally {
        lock.unlock()
    }
}

// غير موصى به للدوال الكبيرة
inline fun largeComputation(...) { // سيء — الجسم >50 سطرًا
    // أكثر من 50 سطرًا — من الأفضل استخراجها في دالة عادية
}

تتطلب الدوال المضمنة العامة في المكتبات الحذر: إذا تغير جسم دالة مضمنة، يجب إعادة ترجمة جميع العملاء. توصي JetBrains باستخدام @PublishedApi internal للأعضاء الذين يتم استدعاؤهم من دوال مضمنة للحفاظ على التوافق داخل الوحدة.

الأسئلة الشائعة

هل يمكن إنشاء دالة امتداد مضمنة؟

نعم، تعمل دالة الامتداد المضمنة بدون قيود. على سبيل المثال: inline fun String.transform(block: (Char) -> Char): String. لا يؤثر الامتداد على إمكانية التضمين — يتعامل معها المترجم بنفس طريقة التعامل مع الدالة المضمنة العادية.

متى لا يوفر inline فوائد؟

إذا كانت الدالة لا تقبل معاملات لامدا — لا يوفر inline أي ميزة. يصدر مترجم Kotlin تحذيرًا: “Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types.” أيضًا، inline ضار للدوال الكبيرة بسبب نمو كود البايت.

كيف يختلف inline عن @JvmInline (value class)؟

inline — معدّل دالة يقوم بتضمين جسم الدالة في موقع الاستدعاء. @JvmInline (value class) — آلية لفئات الغلاف التي يتم استبدالها بقيمتها في وقت الترجمة. مفهومان مختلفان: inline يحسن الاستدعاءات، value class يحسن تمثيل البيانات.

هل يمكن استخدام inline مع دوال suspend؟

لا، لا يمكن أن تكون دوال suspend مضمنة لأنها تُترجم إلى آلة حالة مع Continuation. ومع ذلك، يمكن لدالة مضمنة أن تقبل لامدا suspend كمعامل مع crossinline. يُستخدم هذا غالبًا في coroutines: inline fun launch(block: suspend CoroutineScope.() -> Unit).

هل يؤثر inline على التصحيح؟

نعم، الدوال المضمنة تعقّد التصحيح لأنه لا يتم استدعاء جسم الدالة بل يتم تضمينه في موقع الاستدعاء. تصبح تتبعات الاستدعاء أطول، ونقاط التوقف تعمل ولكنها قد تظهر مواضع غير متوقعة. توصي JetBrains بالتصحيح بدون inline وتفعيله فقط في إصدارات release.

الخلاصة

  • الدالة المضمنة — تضمين جسم الدالة في موقع الاستدعاء، مما يلغي الحمل الزائد للامدا
  • الإرجاع غير المحلي — return من لامدا ينهي الدالة الخارجية (ممكن فقط مع inline)
  • noinline — يمنع تضمين معامل لامدا محدد
  • crossinline — يسمح بالتضمين ولكن يمنع الإرجاع غير المحلي
  • الدوال المتكررة لا يمكن أن تكون مضمنة — المترجم يرجع خطأً
  • حجم كود البايت ينمو — استخدم inline للدوال حتى 10–15 سطرًا
  • الاستخدام الأمثل — الدوال عالية الترتيب مع معاملات لامدا، كتل المزامنة، دوال النطاق (let, apply, also, run)

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا