Статьи Kotlin. Встроенные (inline) функции
Post
Cancel

Kotlin. Встроенные (inline) функции

В Kotlin существует модификатор inline, которым можно пометить функцию. Основное его предназначение - повысить производительность. Чтобы понять за счёт чего она повышается, нужно вспомнить лямбда-выражения.

Как правило, лямбда-выражения компилируются в анонимные классы. То есть каждый раз, когда используется лямбда-выражение, создаётся дополнительный класс. Отсюда вытекают дополнительные накладные расходы у функций, которые принимают лямбду в качестве аргумента. Если же функцию отметить модификатором inline, то компилятор не будет создавать анонимные классы и их объекты для каждого лямбда-выражения, а просто вставит код её реализации в место вызова. Или другими словами встроит её.

1
2
3
inline fun <T> lock(lock: Lock, body: () -> T): T {
    // ...
}

Модификатор inline влияет и на функцию, и на лямбду, переданную ей: они обе будут встроены в место вызова.

Если же вы хотите, чтобы некоторые лямбды, переданные inline-функции, не были встроены, то отметьте их модификатором noinline.

1
2
3
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
    // ...
}

Разница между ними в том, что встраиваемая лямбда может быть вызвана только внутри inline-функции, либо может быть передана в качестве встраиваемого аргумента. В то время как noinline-лямбды можно хранить внутри полей, передавать куда-либо итд.


Return

Иначе работает и оператор return. В лямбда-выражении оператор return завершает работу всей функции, в которой было вызвано лямбда-выражение. Для выхода только из лямбды используется label.

Но если лямбда-выражение передаётся невстраиваемой функции, то использование оператора return недопустимо. Если же лямбда-выражение передаётся в inline-функцию, то оператор return разрешён и он завершает работу этой функции.


Reified

Reified - это ключевое слово, которое может быть использовано только в inline-функциях. Его цель - получение доступа к информации о типе класса.

Параметры, отмеченные этим ключевым словом, ещё называют овеществляемыми.

Допустим у нас есть такая функция:

1
2
3
inline fun <T> genericsExample(value: T) {
    println(value)
}

Она универсальная и может быть использована для любого типа переменной. Такой эффект достигнут благодаря использованию дженериков (обобщения). Если мы захотим узнать тип класса T,

1
2
3
4
inline fun <T> genericsExample(value: T) {
    println(value)
    println("Type of T: ${T::class.java}")
}

то получим ошибку Cannot use 'T' as reified type parameter, так как информация о типе в дженериках отсутствует.

Тут нам на помощь и приходит reified. Ключевое слово указывается перед типом, информацию о котором мы хотим получить внутри функции.

1
2
3
4
inline fun <reified T> genericsExample(value: T) {
    println(value)
    println("Type of T: ${T::class.java}")
}

За кулисами компилятор заменит тип T на фактический, поэтому мы сможем получить о нём информацию и при этом нет необходимости этот тип явно передавать функции.

Также reified может быть использован в другом сценарии: для возврата из функции разных типов данных.

1
2
3
4
5
6
7
inline fun<reified T> showMessage(marks: Int): T {
    return when (T::class) {
        Int::class -> marks as T
        String::class -> "Congratulations! you scored more than 90%" as T
        else -> "Please enter valid type" as T
    }
}

Или для замены ссылок на классы более лаконичным кодом.

1
2
3
4
5
6
7
8
inline fun <reified Т : Activity> Context.startActivity() {
    val intent = Intent(this, Т::class.java)
    startActivity(intent)
}

...

startActivity<DetailActivity>()  

Встроенная функция с reified не может быть вызвана из кода Java. Такие функции требуют дополнительной обработки для подстановки значения типовых аргументов в байт-код, и поэтому всегда должны быть встраиваемыми. А inline-функции можно вызвать из Java только как функции без встраивания.

Почему reified возможно использовать только с встроенными функциями?

Когда мы вызываем inline-функцию, у которой параметр отмечен ключевым словом reified, компилятор определяет тип этого параметра и генерирует байт-код, который ссылается на конкретный класс. А так как в байт-коде указан конкретный класс, а не типовой параметр, типовой аргумент не стирается во время выполнения. Этот байт-код в последующем будет вставляться в точки вызова inline-функции.


Полезные ссылки

Inline Functions - официальная документация.
Встроенные (inline) функции - неофициальный перевод на русский язык.

This post is licensed under CC BY 4.0 by the author.