تابع inline در Kotlin: چیست، نحو و کاربرد

نویسنده: IT Sectr منتشر شده: 2026-06-21 زمان مطالعه: 9 دقیقه

تابع inline — مکانیزمی در Kotlin است که بدنه تابع مستقیماً در هر محل فراخوانی در مرحله کامپایل جایگذاری می‌شود. این کار سربار ایجاد کلاس‌ها و اشیاء ناشناس برای پارامترهای لامبدا را حذف می‌کند. بر اساس Kotlin Documentation, 2025، کلمه کلیدی inline به‌ویژه برای توابع مرتبه بالاتر مؤثر است، جایی که هر لامبدا بدون درج، یک شیء جداگانه FunctionN ایجاد می‌کند و garbage collector را سنگین می‌کند.

نکات اصلی

  • تابع inline — تابعی که بدنه آن در محل فراخوانی در مرحله کامپایل جایگذاری می‌شود
  • کاهش سربار — ایجاد کلاس‌ها و اشیاء ناشناس برای لامبداها را حذف می‌کند
  • nonlocal return — لامبداهای داخل توابع inline می‌توانند return از تابع بیرونی انجام دهند
  • noinline — درج پارامترهای لامبدای خاص را ممنوع می‌کند
  • crossinline — nonlocal return را مجاز می‌کند اما return از بافت تابع inline را ممنوع می‌کند

تابع inline در Kotlin چیست؟

تابع inline تابعی است که با کلمه کلیدی inline مشخص شده است. کامپایلر Kotlin برای آن بایت‌کد جداگانه‌ای با فراخوانی ایجاد نمی‌کند، بلکه بدنه تابع را مستقیماً در هر محل فراخوانی کپی می‌کند. هدف اصلی — بهینه‌سازی توابع مرتبه بالاتر است که عبارت‌های لامبدا را می‌پذیرند، زیرا هر لامبدا در شرایط عادی یک شیء ناشناس از کلاس Function ایجاد می‌کند.

بر اساس JetBrains Tech Blog (2024)، استفاده از توابع inline در Kotlin می‌تواند تعداد اشیاء ایجاد شده را 40–60٪ در توابعی که به شدت از لامبداها استفاده می‌کنند کاهش دهد. در حلقه‌ها و عملیات‌های پربار (مرتب‌سازی، فیلتر کردن مجموعه‌ها) این افزایش عملکرد قابل اندازه‌گیری می‌دهد.

بدون inline هر لامبدا به یک کلاس ناشناس (یا نمونه‌ای از یک رابط تابعی سنتز شده) کامپایل می‌شود. برای لامبداهایی که متغیرها را می‌گیرند، اشیاء wrapper اضافی ایجاد می‌شوند. توابع inline همه این اشیاء را در مرحله کامپایل حذف می‌کنند و آنها را با کد مستقیم با دسترسی به متغیرهای محلی بدون wrapper جایگزین می‌کنند.

فقط برای توابع با پارامترهای لامبدا از inline استفاده کنید — کامپایلر Kotlin خودش در صورت عدم سوددهی inline هشدار می‌دهد.

نحو تابع 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")
}

هیچ شیئی برای لامبدا ایجاد نمی‌شود — کد action مستقیماً اجرا می‌شود. این ماهیت بهینه‌سازی است: به جای فراخوانی Function.invoke() — درج مستقیم کد با بدنه لامبدا.

بررسی از طریق decompile

برای بررسی درج، Tools > Kotlin > Show Kotlin Bytecode را در IntelliJ IDEA باز کنید و Decompile را بزنید. خواهید دید که به جای فراخوانی repeatAction با لامبدا، درج مستقیم بدنه تابع با حلقه for تولید می‌شود.

مشکل سربار لامبداها و راه حل آن

هر لامبدا در Kotlin به یکی از سه نوع کامپایل می‌شود. نوع اول — اگر لامبدا متغیرها را نگیرد، به یک متد ایستای کلاسی که در آن اعلان شده تبدیل می‌شود. نوع دوم — اگر یک متغیر را بگیرد، یک کلاس ناشناس ایجاد می‌شود. نوع سوم — اگر چند متغیر را بگیرد، یک کلاس ناشناس با فیلدهایی برای هر متغیر گرفته شده ایجاد می‌شود.

نوع لامبدابدون inlineبا inline
بدون گرفتنیک متد ایستا (قابل استفاده مجدد)درج کامل، بدون فراخوانی
با گرفتن 1 متغیرکلاس ناشناس (یک شیء)درج کامل، بدون شیء
با گرفتن N متغیرکلاس ناشناس با N فیلددرج کامل، بدون شیء
بازگشتیفراخوانی معمولیinline ممنوع است

بر اساس Android Performance Patterns (Google, 2024)، در برنامه‌هایی با استفاده فشرده از مجموعه‌ها (فیلتر کردن، مرتب‌سازی، گروه‌بندی) توابع inline تعداد تخصیص‌ها را 25–35٪ کاهش می‌دهند. این اثر به‌ویژه در Jetpack Compose قابل توجه است، جایی که هر تغییر وضعیت باعث ترکیب مجدد با لامبداهای متعدد می‌شود.

Nonlocal return و محدودیت‌ها

لامبدا در یک تابع معمولی نمی‌تواند return از تابع بیرونی انجام دهد — فقط return محلی از خود لامبدا (از طریق return@label). در تابع inline، لامبدا در بدنه تابع فراخواننده جایگذاری می‌شود، بنابراین nonlocal return ممکن می‌شود: 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() برمی‌گرداند
}

Nonlocal return برای خاتمه زودهنگام مفید است، اما می‌تواند منجر به خطا شود. اگر لامبدا در بافت غیرمحلی استفاده شود (در یک متغیر ذخیره شود)، nonlocal return باعث RuntimeException می‌شود. کامپایلر Kotlin در تلاش برای چنین ذخیره‌ای هشدار می‌دهد.

noinline و crossinline: مدیریت درج

وقتی یک تابع چندین پارامتر لامبدا دارد، گاهی لازم است فقط بخشی از آنها درج شوند. برای این کار از noinline استفاده می‌شود — درج پارامتر لامبدای خاص را ممنوع می‌کند و آن را به عنوان یک شیء Function معمولی باقی می‌گذارد.

تغییردهنده crossinline مسئله مخالف را حل می‌کند: لامبدا درج می‌شود، اما nonlocal return ممنوع می‌شود. این زمانی لازم است که لامبدا داخل لامبدای دیگری یا در بافتی که 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 علامت‌گذاری شده — درج نمی‌شود، می‌توان آن را به عنوان شیء منتقل کرد، در فیلد کلاس ذخیره کرد یا به عنوان listener استفاده کرد.

محدودیت‌های توابع inline و توصیه‌ها

توابع inline محدودیت‌هایی دارند. توابع بازگشتی inline ممنوع هستند — کامپایلر خطا می‌دهد. توابع inline نمی‌توانند visibility private یا internal داشته باشند اگر در ماژول دیگری اعلان شده باشند، اما این محدودیت به دسترسی مربوط است، نه به خود مکانیزم درج.

اندازه بایت‌کد با هر فراخوانی تابع inline افزایش می‌یابد، زیرا بدنه کپی می‌شود. بر اساس Kotlin Coding Conventions (JetBrains, 2025)، توصیه می‌شود فقط برای توابع تا 10–15 خط از inline استفاده کنید. برای توابع بزرگ، سود حاصل از درج لامبداها ممکن است با افزایش اندازه 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 خط — بهتر است به یک تابع معمولی استخراج شود
}

توابع عمومی inline در کتابخانه‌ها نیاز به احتیاط دارند: اگر بدنه تابع inline تغییر کند، همه کلاینت‌ها باید دوباره کامپایل شوند. JetBrains استفاده از @PublishedApi internal را برای اعضایی که از توابع inline فراخوانی می‌شوند توصیه می‌کند تا سازگاری درون ماژول حفظ شود.

سوالات متداول

آیا می‌توان تابع extension inline ساخت؟

بله، تابع extension inline بدون محدودیت کار می‌کند. مثلاً: inline fun String.transform(block: (Char) -> Char): String. extension بر قابلیت درج تأثیر نمی‌گذارد — کامپایلر آن را مانند یک تابع inline معمولی پردازش می‌کند.

چه زمانی 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) — مکانیزمی برای کلاس‌های wrapper است که در زمان کامپایل با مقدار جایگزین می‌شوند. مفاهیم متفاوت: inline فراخوانی‌ها را بهینه می‌کند، value class نمایش داده‌ها را بهینه می‌کند.

آیا می‌توان از inline با توابع suspend استفاده کرد؟

خیر، توابع suspend نمی‌توانند inline باشند زیرا آنها به ماشین حالت با Continuation کامپایل می‌شوند. با این حال، تابع inline می‌تواند لامبدای suspend را به عنوان پارامتر با crossinline بپذیرد. این اغلب در کروتین‌ها استفاده می‌شود: inline fun launch(block: suspend CoroutineScope.() -> Unit).

آیا inline بر دیباگ تأثیر می‌گذارد؟

بله، توابع inline دیباگ را دشوارتر می‌کنند، زیرا بدنه تابع فراخوانی نمی‌شود، بلکه در محل فراخوانی درج می‌شود. Stacktrace طولانی‌تر می‌شود، نقاط توقف کار می‌کنند، اما ممکن است موقعیت‌های غیرمنتظره نشان دهند. JetBrains توصیه می‌کند بدون inline دیباگ کنید و آن را فقط در نسخه‌های release فعال کنید.

خلاصه

  • تابع inline — بدنه تابع را در محل فراخوانی درج می‌کند و سربار لامبداها را حذف می‌کند
  • Nonlocal return — return از لامبدا تابع بیرونی را خاتمه می‌دهد (فقط در inline ممکن است)
  • noinline — درج پارامتر لامبدای خاص را ممنوع می‌کند
  • crossinline — درج را مجاز می‌کند اما nonlocal return را ممنوع می‌کند
  • توابع بازگشتی نمی‌توانند inline باشند — کامپایلر خطا می‌دهد
  • اندازه بایت‌کد افزایش می‌یابد — از inline برای توابع تا 10–15 خط استفاده کنید
  • کاربرد بهینه — توابع مرتبه بالاتر با پارامترهای لامبدا، بلوک‌های همگام‌سازی، scope functions (let, apply, also, run)

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید