تابع inline — مکانیزمی در Kotlin است که بدنه تابع مستقیماً در هر محل فراخوانی در مرحله کامپایل جایگذاری میشود. این کار سربار ایجاد کلاسها و اشیاء ناشناس برای پارامترهای لامبدا را حذف میکند. بر اساس Kotlin Documentation, 2025، کلمه کلیدی inline بهویژه برای توابع مرتبه بالاتر مؤثر است، جایی که هر لامبدا بدون درج، یک شیء جداگانه FunctionN ایجاد میکند و garbage collector را سنگین میکند.
نکات اصلی
تابع inline تابعی است که با کلمه کلیدی inline مشخص شده است. کامپایلر Kotlin برای آن بایتکد جداگانهای با فراخوانی ایجاد نمیکند، بلکه بدنه تابع را مستقیماً در هر محل فراخوانی کپی میکند. هدف اصلی — بهینهسازی توابع مرتبه بالاتر است که عبارتهای لامبدا را میپذیرند، زیرا هر لامبدا در شرایط عادی یک شیء ناشناس از کلاس Function ایجاد میکند.
بر اساس JetBrains Tech Blog (2024)، استفاده از توابع inline در Kotlin میتواند تعداد اشیاء ایجاد شده را 40–60٪ در توابعی که به شدت از لامبداها استفاده میکنند کاهش دهد. در حلقهها و عملیاتهای پربار (مرتبسازی، فیلتر کردن مجموعهها) این افزایش عملکرد قابل اندازهگیری میدهد.
بدون inline هر لامبدا به یک کلاس ناشناس (یا نمونهای از یک رابط تابعی سنتز شده) کامپایل میشود. برای لامبداهایی که متغیرها را میگیرند، اشیاء wrapper اضافی ایجاد میشوند. توابع inline همه این اشیاء را در مرحله کامپایل حذف میکنند و آنها را با کد مستقیم با دسترسی به متغیرهای محلی بدون wrapper جایگزین میکنند.
فقط برای توابع با پارامترهای لامبدا از inline استفاده کنید — کامپایلر Kotlin خودش در صورت عدم سوددهی inline هشدار میدهد.
کافی است کلمه کلیدی inline را قبل از اعلان تابع اضافه کنید. کامپایلر به طور خودکار بدنه تابع را در محلهای فراخوانی جایگذاری میکند. خود تابع در بایتکد برای مواردی که مستقیماً فراخوانی نمیشود (مثلاً از کد Java) باقی میماند.
inline fun Int.repeatAction(action: (Int) -> Unit) {
for (i in 0 until this) {
action(i)
}
}
// فراخوانی — کد لامبدا در بدنه تابع درج میشود
5.repeatAction { index ->
println("Index: $index")
}
پس از کامپایل، کد بالا معادل خواهد بود با:
// بعد از درج چه اتفاقی میافتد (به صورت شماتیک):
val $this = 5
for (i in 0 until $this) {
println("Index: $i")
}
هیچ شیئی برای لامبدا ایجاد نمیشود — کد action مستقیماً اجرا میشود. این ماهیت بهینهسازی است: به جای فراخوانی Function.invoke() — درج مستقیم کد با بدنه لامبدا.
برای بررسی درج، 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 قابل توجه است، جایی که هر تغییر وضعیت باعث ترکیب مجدد با لامبداهای متعدد میشود.
لامبدا در یک تابع معمولی نمیتواند return از تابع بیرونی انجام دهد — فقط return محلی از خود لامبدا (از طریق return@label). در تابع inline، لامبدا در بدنه تابع فراخواننده جایگذاری میشود، بنابراین nonlocal return ممکن میشود: return داخل لامبدا تابع بیرونی را خاتمه میدهد.
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 استفاده میشود — درج پارامتر لامبدای خاص را ممنوع میکند و آن را به عنوان یک شیء Function معمولی باقی میگذارد.
تغییردهنده crossinline مسئله مخالف را حل میکند: لامبدا درج میشود، اما nonlocal return ممنوع میشود. این زمانی لازم است که لامبدا داخل لامبدای دیگری یا در بافتی که return مجاز نیست استفاده شود (مثلاً به Runnable منتقل میشود).
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 نمیتوانند visibility private یا internal داشته باشند اگر در ماژول دیگری اعلان شده باشند، اما این محدودیت به دسترسی مربوط است، نه به خود مکانیزم درج.
اندازه بایتکد با هر فراخوانی تابع inline افزایش مییابد، زیرا بدنه کپی میشود. بر اساس Kotlin Coding Conventions (JetBrains, 2025)، توصیه میشود فقط برای توابع تا 10–15 خط از inline استفاده کنید. برای توابع بزرگ، سود حاصل از درج لامبداها ممکن است با افزایش اندازه APK خنثی شود (در Android این حیاتی است — محدودیت 64K متد وجود دارد).
// روش توصیه شده
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 بدون محدودیت کار میکند. مثلاً: inline fun String.transform(block: (Char) -> Char): String. extension بر قابلیت درج تأثیر نمیگذارد — کامپایلر آن را مانند یک تابع 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) — مکانیزمی برای کلاسهای wrapper است که در زمان کامپایل با مقدار جایگزین میشوند. مفاهیم متفاوت: inline فراخوانیها را بهینه میکند، value class نمایش دادهها را بهینه میکند.
خیر، توابع suspend نمیتوانند inline باشند زیرا آنها به ماشین حالت با Continuation کامپایل میشوند. با این حال، تابع inline میتواند لامبدای suspend را به عنوان پارامتر با crossinline بپذیرد. این اغلب در کروتینها استفاده میشود: inline fun launch(block: suspend CoroutineScope.() -> Unit).
بله، توابع inline دیباگ را دشوارتر میکنند، زیرا بدنه تابع فراخوانی نمیشود، بلکه در محل فراخوانی درج میشود. Stacktrace طولانیتر میشود، نقاط توقف کار میکنند، اما ممکن است موقعیتهای غیرمنتظره نشان دهند. JetBrains توصیه میکند بدون inline دیباگ کنید و آن را فقط در نسخههای release فعال کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید