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

В чем разница между передачей int и lambda в inline функцию?

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

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

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

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

Разница между передачей int и lambda в inline функцию

Основное отличие

Ключевая разница заключается в способе обработки аргументов компилятором Kotlin при использовании модификатора inline.

1. Обработка обычных примитивов (int)

Когда передается примитивный тип (например, Int), компилятор просто встраивает значение в место вызова:

inline fun processNumber(number: Int, action: (Int) -> Unit) {
    action(number)
}

// Вызов
fun main() {
    processNumber(5) { println(it * 2) }
}

После компиляции код преобразуется примерно так:

fun main() {
    val number = 5
    println(number * 2)  // Лямбда встроена напрямую
}

2. Обработка лямбда-выражений

С лямбдами ситуация сложнее, так как Kotlin по умолчанию создает объекты для каждого лямбда-выражения:

inline fun processNumber(number: Int, action: (Int) -> Unit) {
    action(number)
}

fun main() {
    val multiplier = 2
    processNumber(5) { println(it * multiplier) }
}

Без inline компилятор создал бы анонимный класс:

fun main() {
    val multiplier = 2
    val action = object : Function1<Int, Unit> {
        override fun invoke(it: Int) {
            println(it * multiplier)
        }
    }
    processNumber(5, action)
}

С inline лямбда встраивается непосредственно в код:

fun main() {
    val multiplier = 2
    val number = 5
    println(number * multiplier)  // Полностью встроенный код
}

3. Ключевые различия в деталях

Для int:

  • Простое копирование значения
  • Нет накладных расходов на создание объектов
  • Минимальный impact на размер байт-кода
  • Работает одинаково эффективно в inline и non-inline функциях

Для lambda:

  • Без inline: Создается объект FunctionN для каждой лямбды
  • С inline: Код лямбды встраивается непосредственно, объекты не создаются
  • Захват внешних переменных: Требует специальной обработки в не-inline случае

4. Особый случай: noinline параметры

Если нужно передать лямбду, но не инлайнить ее, используется модификатор noinline:

inline fun process(
    number: Int,
    noinline validator: (Int) -> Boolean,  // Не будет встроена!
    processor: (Int) -> Unit              // Будет встроена
) {
    if (validator(number)) {
        processor(number)
    }
}

5. crossinline для контроля встраивания

Когда лямбда используется внутри локальных scope или нелокальных return:

inline fun process(crossinline action: () -> Unit) {
    Runnable { action() }.run()  // crossinline запрещает нелокальные return
}

6. Производительность и накладные расходы

Аспектint аргументlambda аргумент
Выделение памятиНет (примитив)Объект (без inline)
Вызов методаНет (встроено)Виртуальный вызов (без inline)
Захват контекстаНе требуетсяТребует создания замыкания
Размер кодаМинимальный ростМожет значительно увеличиться

7. Практический пример с сравнением

// Non-inline версия (проблемы с производительностью)
fun processNonInline(numbers: List<Int>, action: (Int) -> Unit) {
    numbers.forEach { action(it) }
}

// Inline версия (оптимизированная)
inline fun processInline(numbers: List<Int>, action: (Int) -> Unit) {
    numbers.forEach { action(it) }
}

fun benchmark() {
    val list = (1..1000).toList()
    
    // Создает 1000 объектов Function1
    processNonInline(list) { it * 2 }
    
    // Не создает объектов - весь код встроен
    processInline(list) { it * 2 }
}

8. Когда использовать inline с лямбдами

✅ Использовать inline когда:

  • Функция принимает лямбда-параметры
  • Функция небольшая по размеру
  • Критична производительность (коллекции, DSL)
  • Нужно использовать reified типы

❌ Избегать inline когда:

  • Функция большая (увеличит размер бинарника)
  • Рекурсивные функции
  • Лямбда передается в noinline параметр

9. Специальный случай: reified типы

Только с inline функциями можно использовать reified типы, что невозможно с примитивами:

inline fun <reified T> checkType(value: Any): Boolean {
    return value is T  // Доступ к типу в runtime!
}

Выводы

  1. int и другие примитивы передаются по значению и инлайнятся тривиально
  2. Лямбды без inline создают объекты, что может снижать производительность
  3. Лямбды с inline полностью встраивают свой код, устраняя накладные расходы
  4. Решение об инлайнинге должно учитывать баланс между производительностью и размером кода
  5. Специальные модификаторы (noinline, crossinline) позволяют точно контролировать поведение

Инлайнинг особенно важен для высокоуровневых функций Kotlin (map, filter, let, apply), которые активно используются в повседневной разработке и должны быть максимально эффективными.