← Назад к вопросам

Можно ли делать каждую функцию inline?

2.0 Middle🔥 141 комментариев
#JVM и память#Kotlin основы#Производительность и оптимизация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Можно ли делать каждую функцию inline?

Короткий ответ

Нет, не нужно. Inline функции имеют свои trade-offs, и их следует использовать избирательно для конкретных случаев.

Что такое inline функции

Inline функция — это функция, код которой копируется (inlining) в место вызова во время компиляции, вместо того чтобы создавать вызов функции на runtime.

// Обычная функция
fun add(a: Int, b: Int): Int {
    return a + b
}
val result = add(1, 2)  // Вызов функции

// Inline функция
inline fun addInline(a: Int, b: Int): Int {
    return a + b
}
val result = addInline(1, 2)  // Код подставляется прямо сюда

// После компиляции (inline):
val result = 1 + 2  // Нет вызова функции!

Преимущества inline функций

1. Производительность

Исключается overhead вызова функции (создание stack frame, переход по адресу).

// Без inline — много вызовов в цикле
for (i in 1..1000000) {
    val sum = add(i, 1)  // 1 млн вызовов
}

// С inline — прямое добавление, быстрее
inline fun add(a: Int, b: Int) = a + b
for (i in 1..1000000) {
    val sum = add(i, 1)  // Код подставляется, нет вызовов
}

2. Lambda без allocation

Самое важное применение inline — это передача lambda без создания объекта.

// БЕЗ inline — lambda оборачивается в объект Function
fun forEach(action: (Int) -> Unit) {
    for (i in 1..10) {
        action(i)  // Вызов через Function объект
    }
}

listOf(1, 2, 3).forEach { println(it) }  // Создаёт Function объект

// С inline — lambda код подставляется прямо
inline fun forEach(action: (Int) -> Unit) {
    for (i in 1..10) {
        action(i)  // Код lambda подставляется сюда
    }
}

listOf(1, 2, 3).forEach { println(it) }  // БЕЗ allocation!

Это особенно важно для часто вызываемых функций (map, filter, forEach и т.д.).

Недостатки inline функций

1. Увеличение размера бинарника (APK/JAR)

Каждый вызов inline функции копирует её код, что увеличивает размер:

inline fun add(a: Int, b: Int): Int = a + b

// Если функцию вызвать 100 раз
val r1 = add(1, 2)
val r2 = add(2, 3)
val r3 = add(3, 4)
// ...
val r100 = add(99, 100)

// Компилятор скопирует код 100 раз (увеличит размер)

Для мобильных приложений это может быть критично:

  • APK будет больше
  • Больше памяти на загрузку
  • Дольше инициализация

2. Нельзя использовать в некоторых местах

Inline функции нельзя:

  • Передавать как параметр (нельзя получить reference)
  • Использовать как interface метод
  • Вызывать из другого модуля (если не public inline)
inline fun myFunc() { }

// ✗ Ошибка — нельзя получить reference
val funcRef: () -> Unit = ::myFunc

// ✗ Нельзя переопределить
interface MyInterface {
    inline fun doSomething()  // Синтаксическая ошибка
}

3. Сложнее отладка

При отладке сложнее прослеживать стек вызовов, т.к. функции нет на runtime:

inline fun getValue(): Int = 42

fun process() {
    val value = getValue()  // После компиляции это просто: val value = 42
    // В stack trace не будет вызова getValue()
}

4. Риск увеличить размер критичного метода

Если inline функция вызывается много раз, код может раздуться:

inline fun complexLogic(): String {
    // 50 строк кода
    return "result"
}

// Если вызвать 10 раз — 500 строк в бинарнике
for (i in 1..10) {
    val result = complexLogic()
}

Когда использовать inline

✓ ИСПОЛЬЗУЙ inline в этих случаях:

1. Функции высокого порядка (высокочастотные)

// Стандартная библиотека делает это
inline fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T> {
    val result = mutableListOf<T>()
    for (item in this) {
        if (predicate(item)) result.add(item)  // Lambda подставляется
    }
    return result
}

// Использование без allocation
val evenNumbers = numbers.filter { it % 2 == 0 }

2. Функции с reified type parameters

// Нужен inline для доступа к Type параметру
inline fun <reified T> parseJson(json: String): T {
    val type = T::class
    return json.parseAs(type)
}

// Работает только с inline
val user = parseJson<User>(jsonString)  // Type информация доступна

3. Очень маленькие функции

// Одна строка — inline имеет смысл
inline fun isEven(x: Int) = x % 2 == 0

// Или простой getter
inline fun getValue() = cachedValue

4. Функции для DSL

// DSL часто использует inline
inline fun html(body: HtmlBuilder.() -> Unit): String {
    return HtmlBuilder().apply(body).build()
}

val html = html {
    div {
        p { text("Hello") }
    }
}

✗ НЕ используй inline в этих случаях:

1. Сложные функции

// ✗ Плохо — код раздуется
inline fun processData(): String {
    // 100 строк логики
    // 10 вложенных условий
    // 5 вспомогательных вызовов
    return "result"
}

2. Функции, вызываемые из разных мест

// ✗ Плохо — код скопируется везде
inline fun calculateTotal(items: List<Int>): Int {
    var sum = 0
    for (item in items) sum += item
    return sum
}

// Если вызвать из 50 методов — 50 копий кода

3. Рекурсивные функции

// ✗ Ошибка компиляции
inline fun factorial(n: Int): Int {
    return if (n <= 1) 1 else n * factorial(n - 1)  // Рекурсия не работает
}

4. Функции, возвращающие функции

// ✗ Сложно с inline
inline fun getValidator(): (String) -> Boolean {
    return { it.isNotEmpty() }
}

Практические примеры из стандартной библиотеки

Collections API

// Все эти inline для оптимизации
inline fun <T> Iterable<T>.forEach(action: (T) -> Unit) { ... }
inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> { ... }
inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> { ... }
inline fun <T> Iterable<T>.find(predicate: (T) -> Boolean): T? { ... }

Scope functions

// Inline для lambda без allocation
inline fun <T> T.apply(block: T.() -> Unit): T { ... }
inline fun <T, R> T.let(block: (T) -> R): R { ... }
inline fun <T> T.also(block: (T) -> Unit): T { ... }

Правило большого пальца

Используй inline ТОЛЬКО если:
1. Функция маленькая (< 10 строк), ИЛИ
2. Функция передаёт lambda без allocation, ИЛИ
3. Функция использует reified type parameters

ИНАЧЕ — не используй inline

Вывод

  • inline улучшает производительность для маленьких функций с lambda параметрами
  • inline увеличивает размер бинарника — это overhead для мобильных
  • Стандартная библиотека использует inline разумно — используй её как образец
  • Компилятор часто инлайнирует автоматически (JVM optimization)
  • Не преждевременная оптимизация — используй inline только где есть реальная потребность
Можно ли делать каждую функцию inline? | PrepBro