Inline функція в Kotlin: що це, синтаксис та застосування

Автор: IT Sectr Опубліковано: 2026-06-21 Час читання: 9 хв

inline функція — механізм Kotlin, при якому тіло функції підставляється безпосередньо в кожне місце виклику на етапі компіляції. Це усуває overhead від створення анонімних класів та об'єктів для лямбда-параметрів. За даними Kotlin Documentation, 2025, ключове слово inline особливо ефективне для higher-order функцій, де кожна лямбда без вбудовування створює окремий об'єкт FunctionN, навантажуючи збирач сміття.

Головне

  • inline функція — функція, тіло якої вбудовується в місце виклику на етапі компіляції
  • Зниження overhead — усуває створення анонімних класів та об'єктів для лямбд
  • Nonlocal return — лямбди всередині inline-функцій можуть виконувати return із зовнішньої функції
  • noinline — забороняє вбудовування окремих лямбда-параметрів
  • crossinline — дозволяє nonlocal return, але забороняє return із контексту inline-функції

Що таке inline функція в Kotlin?

Inline функція — це функція, позначена ключовим словом inline. Компілятор Kotlin не створює для неї окремого байт-коду з викликом, а копіює тіло функції безпосередньо в кожне місце виклику. Основна мета — оптимізація higher-order функцій, які приймають лямбда-вирази, оскільки кожна лямбда у звичайній ситуації створює анонімний об'єкт класу Function.

За даними JetBrains Tech Blog (2024), використання inline-функцій в Kotlin може скоротити кількість створюваних об'єктів на 40–60% у функціях, що інтенсивно використовують лямбди. У циклах та високонавантажених операціях (сортування, фільтрація колекцій) це дає вимірюваний приріст продуктивності.

Без inline кожна лямбда компілюється в анонімний клас (або інстанс синтезованого функціонального інтерфейсу). Для лямбд, що захоплюють змінні, створюються додаткові об'єкти-обгортки. Inline-функції усувають всі ці об'єкти на етапі компіляції, замінюючи їх прямим кодом з доступом до локальних змінних без обгорток.

Використовуйте 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() — пряма вставка коду з тілом лямбди.

Перевірка через декомпіляцію

Для перевірки вбудовування відкрийте Tools > Kotlin > Show Kotlin Bytecode в IntelliJ IDEA та натисніть Decompile. Ви побачите, що замість виклику repeatAction з лямбдою генерується пряма вставка тіла функції з циклом for.

Проблема overhead лямбд та її вирішення

Кожна лямбда в 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-функції не можуть мати видимість private або internal, якщо вони оголошені в іншому модулі, але це обмеження пов'язане з доступом, а не з самим механізмом вбудовування.

Розмір байт-коду зростає з кожним викликом inline-функції, оскільки тіло копіюється. За даними 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 рядків — краще винести у звичайну функцію
}

Публічні inline-функції в бібліотеках вимагають обережності: якщо тіло inline-функції зміниться, всі клієнти повинні перекомпілюватися. JetBrains рекомендує використовувати @PublishedApi internal для членів, які викликаються з inline-функцій, щоб зберегти сумісність у рамках модуля.

Часті запитання

Чи можна зробити inline extension function?

Так, inline extension function працює без обмежень. Наприклад: inline fun String.transform(block: (Char) -> Char): String. Розширення не впливає на можливість вбудовування — компілятор обробляє її так само, як звичайну 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) — механізм для класів-обгорток, які замінюються на значення під час компіляції. Різні концепції: inline оптимізує виклики, value class оптимізує представлення даних.

Чи можна використовувати inline із suspend-функціями?

Ні, suspend-функції не можуть бути inline, оскільки вони компілюються в state-машину з Continuation. Однак inline-функція може приймати suspend-лямбду як параметр із crossinline. Це часто використовується в корутинах: inline fun launch(block: suspend CoroutineScope.() -> Unit).

Чи впливає inline на налагодження?

Так, inline-функції ускладнюють налагодження, оскільки тіло функції не викликається, а вбудовується в місце виклику. Stacktrace стає довшим, точки зупину працюють, але можуть показувати неочікувані позиції. JetBrains рекомендує налагоджувати без inline, а вмикати його тільки в release-збірках.

Підсумки

  • inline функція — вбудовує тіло функції в місце виклику, усуваючи overhead лямбд
  • Nonlocal return — return із лямбди завершує зовнішню функцію (можливо тільки в inline)
  • noinline — забороняє вбудовування конкретного лямбда-параметра
  • crossinline — дозволяє вбудовування, але забороняє nonlocal return
  • Рекурсивні функції не можуть бути inline — компілятор видає помилку
  • Розмір байт-коду зростає — використовуйте inline для функцій до 10–15 рядків
  • Оптимальне застосування — higher-order функції з лямбда-параметрами, блоки синхронізації, scope functions (let, apply, also, run)

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також