inline function — механизм Kotlin, при котором тело функции подставляется непосредственно в каждое место вызова на этапе компиляции. Это устраняет overhead от создания анонимных классов и объектов для лямбда-параметров. По данным Kotlin Documentation, 2025, ключевое слово inline особенно эффективно для higher-order функций, где каждая лямбда без встраивания создаёт отдельный объект FunctionN, нагружая сборщик мусора.
Главное
inline function — это функция, помеченная ключевым словом inline. Компилятор Kotlin не создаёт для неё отдельного байт-кода с вызовом, а копирует тело функции непосредственно в каждое место вызова. Основная цель — оптимизация higher-order функций, которые принимают лямбда-выражения, поскольку каждая лямбда в обычной ситуации создаёт анонимный объект класса Function.
По данным JetBrains Tech Blog (2024), использование inline-функций в Kotlin может сократить количество создаваемых объектов на 40–60% в функциях, интенсивно использующих лямбды. В циклах и высоконагруженных операциях (сортировка, фильтрация коллекций) это даёт измеримый прирост производительности.
Без inline каждая лямбда компилируется в анонимный класс (или инстанс синтезированного функционального интерфейса). Для лямбд, захватывающих переменные, создаются дополнительные объекты-обёртки. Inline-функции устраняют все эти объекты на этапе компиляции, заменяя их прямым кодом с доступом к локальным переменным без обёрток.
Используйте inline только для функций с лямбда-параметрами — компилятор Kotlin сам предупреждает, если inline не даёт выигрыша.
Достаточно добавить ключевое слово inline перед объявлением функции. Компилятор автоматически подставляет тело функции в места вызова. Сама функция продолжает существовать в байт-коде для случаев, когда её вызывают не напрямую (например, из Java-кода).
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")
}
После компиляции код выше будет эквивалентен:
// 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.
Каждая лямбда в 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 in lambda would return null from 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 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-функции не могут иметь видимость private или internal, если они объявлены в другом модуле, но это ограничение связано с доступом, а не с самим механизмом встраивания.
Размер байт-кода растёт с каждым вызовом inline-функции, так как тело копируется. По данным Kotlin Coding Conventions (JetBrains, 2025), рекомендуется использовать inline только для функций размером до 10–15 строк. Для больших функций выигрыш от встраивания лямбд может быть нивелирован увеличением размера APK (в Android это критично — есть лимит 64K методов).
// 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 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 оптимизирует вызовы, value class оптимизирует представление данных.
Нет, suspend-функции не могут быть inline, поскольку они компилируются в state-машину с Continuation. Однако inline-функция может принимать suspend-лямбду как параметр с crossinline. Это часто используется в корутинах: inline fun launch(block: suspend CoroutineScope.() -> Unit).
Да, inline-функции усложняют отладку, так как тело функции не вызывается, а встраивается в место вызова. Stacktrace становится длиннее, точки останова работают, но могут показывать неожиданные позиции. JetBrains рекомендует отлаживать без inline, а включать его только в release-сборках.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также