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

Ответ

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

Основные случаи использования:

  1. Функции-расширения коллекций (map, filter)
  2. Лямбда-параметры высокоуровневых функций
  3. Когда нужно избежать создания анонимного класса для лямбды

Пример:

inline fun <T> Collection<T>.filterInline(
    predicate: (T) -> Boolean
): List<T> {
    val result = mutableListOf<T>()
    for (item in this) {
        if (predicate(item)) result.add(item)
    }
    return result
}

Ограничения:

  • Нельзя использовать для больших функций (максимум 2-3 строки)
  • Нельзя использовать с reified и обычными generic-параметрами одновременно
  • Нельзя вызывать из Java-кода

Ответ 18+ 🔞

Слушай, вот объясню тебе про эти inline-функции в Kotlin, чтобы ты не ломал голову, как я в своё время. Это, блядь, такая штука, которая вместо того чтобы вызываться как нормальная функция, просто вставляется прямо в код, где её используют. Представь, что ты пишешь одно и то же действие в трёх местах, а потом думаешь: "ёпта, да похуй, вынесу в функцию". А потом компилятор смотрит на твой красивый код и такой: "Э, сабака сука, зачем тебе лишний вызов?" — и просто разворачивает её обратно, как было. Хуй с горы, только без дублирования кода.

Основная фишка — производительность, особенно когда ты в лямбдах залип. Без inline каждая твоя красивая лямбда превращается в анонимный класс, а это, ядрёна вошь, лишние объекты и работа для сборщика мусора. А с inline — всё просто встаёт на место, как будто ты это вручную написал. Удивление пиздец, как это работает.

Вот тебе классика, где это реально нужно:

  1. Функции-расширения для коллекций. Ну, те самые map, filter, которые ты каждый день юзаешь. Они все inline, потому что иначе за каждую операцию над списком из трёх элементов JVM создавала бы отдельный объект — пиздопроебибна какая-то.
  2. Когда передаёшь лямбду в функцию. Если твоя функция принимает на вход другую функцию (лямбду), и ты её помечаешь как inline, то эта лямбда тоже "встроится" и не создаст лишний объект. Доверия ебать ноль к лишним аллокациям!
  3. Когда хочешь избежать создания анонимного класса. Это, собственно, и есть причина всего этого цирка. Просто чтобы не засорять память ерундой.

Вот, смотри, как это выглядит в коде. Ничего сложного, обычная проверка коллекции:

inline fun <T> Collection<T>.filterInline(
    predicate: (T) -> Boolean
): List<T> {
    val result = mutableListOf<T>()
    for (item in this) {
        if (predicate(item)) result.add(item)
    }
    return result
}

Видишь этот inline в начале? Всё, магия началась. Когда ты вызовешь list.filterInline { it > 5 }, компилятор возьмёт тело этой функции и воткнёт его прямо в место вызова, вместо того чтобы прыгать по ссылкам. Волнение ебать, какая экономия!

Но и тут, конечно, свои подводные камни, куда ж без них. Ограничения, блядь:

  • Не для монстров. Суть в том, чтобы функция была маленькой, буквально 2-3 строчки. Если ты туда целый роман впихнешь, то размер твоего скомпилированного байт-кода раздуется, как шар перед грозой. Терпения ноль ебать у того, кто будет это дебажить.
  • С дженериками осторожно. Есть такая крутая фича — reified type parameters (когда ты внутри inline-функции можешь узнать реальный тип T). Так вот, её нельзя просто так взять и смешать с обычными дженерик-параметрами в не-inline частях. Голову сломаешь.
  • Из Java — нихуя. Ну, тут всё понятно. Эта магия работает только в мире Kotlin. Если твой код дергают из Java, то inline-функция для них будет выглядеть как самая обычная. Никакого фокуса.

Короче, юзай это дело с умом. Для мелких, часто вызываемых операций — самое то. Для всего остального — обычные функции. Главное — не пытайся быть умнее компилятора, а то будет тебе хиросима и нигерсраки в кодовой базе.