Inline function в Kotlin: что это, синтаксис и применение

Автор: IT Sectr Опубликовано: 2026-06-21 Время чтения: 9 мин

inline function — механизм Kotlin, при котором тело функции подставляется непосредственно в каждое место вызова на этапе компиляции. Это устраняет overhead от создания анонимных классов и объектов для лямбда-параметров. По данным Kotlin Documentation, 2025, ключевое слово inline особенно эффективно для higher-order функций, где каждая лямбда без встраивания создаёт отдельный объект FunctionN, нагружая сборщик мусора.

Главное

  • inline function — функция, тело которой встраивается в место вызова на этапе компиляции
  • Снижение overhead — устраняет создание анонимных классов и объектов для лямбд
  • nonlocal return — лямбды внутри inline-функций могут выполнять return из внешней функции
  • noinline — запрещает встраивание отдельных лямбда-параметров
  • crossinline — разрешает nonlocal return, но запрещает return из контекста inline-функции

Что такое inline function в Kotlin?

inline function — это функция, помеченная ключевым словом inline. Компилятор Kotlin не создаёт для неё отдельного байт-кода с вызовом, а копирует тело функции непосредственно в каждое место вызова. Основная цель — оптимизация higher-order функций, которые принимают лямбда-выражения, поскольку каждая лямбда в обычной ситуации создаёт анонимный объект класса Function.

По данным JetBrains Tech Blog (2024), использование inline-функций в Kotlin может сократить количество создаваемых объектов на 40–60% в функциях, интенсивно использующих лямбды. В циклах и высоконагруженных операциях (сортировка, фильтрация коллекций) это даёт измеримый прирост производительности.

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

Используйте inline только для функций с лямбда-параметрами — компилятор Kotlin сам предупреждает, если inline не даёт выигрыша.

Синтаксис inline function и механизм работы

Достаточно добавить ключевое слово inline перед объявлением функции. Компилятор автоматически подставляет тело функции в места вызова. Сама функция продолжает существовать в байт-коде для случаев, когда её вызывают не напрямую (например, из Java-кода).

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

// Call — lambda code is inlined into the function body
5.repeatAction { index ->
    println("Index: $index")
}

После компиляции код выше будет эквивалентен:

kotlin
// What happens after inlining (schematically):
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 in lambda would return null from 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 can be stored in a variable or passed elsewhere
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
// Recommended practice
inline fun withLock(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
        return action()
    } finally {
        lock.unlock()
    }
}

// Not recommended for large functions
inline fun largeComputation(...) { // bad — body >50 lines
    // more than 50 lines — better to extract into a regular function
}

Публичные 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 function — встраивает тело функции в место вызова, устраняя 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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