Функции высшего порядка очень полезны и действительно могут улучшить reusability
код. Однако одна из самых больших проблем при их использовании - это эффективность. Лямбда-выражения компилируются в классы (часто анонимные классы), а создание объектов в Java - сложная операция. Мы по-прежнему можем эффективно использовать функции высшего порядка, сохраняя при этом все преимущества, сделав функции встроенными.
вот встроенная функция в картинку
Если функция помечена как inline
, во время компиляции кода компилятор заменит все вызовы функции фактическим телом функции. Кроме того, лямбда-выражения, предоставленные в качестве аргументов, заменяются их фактическим телом. Они будут рассматриваться не как функции, а как реальный код.
Вкратце: - Inline -> вместо того, чтобы быть вызванными, они заменяются кодом тела функции во время компиляции ...
В Kotlin использование функции в качестве параметра другой функции (так называемые функции высшего порядка) кажется более естественным, чем в Java.
Однако использование лямбда-выражений имеет некоторые недостатки. Поскольку это анонимные классы (и, следовательно, объекты), им нужна память (и они могут даже увеличивать общее количество методов вашего приложения). Чтобы этого избежать, мы можем встроить наши методы.
fun notInlined(getString: () -> String?) = println(getString())
inline fun inlined(getString: () -> String?) = println(getString())
Из приведенного выше примера : - Эти две функции делают одно и то же - выводят результат функции getString. Один встроен, а другой нет.
Если вы проверите декомпилированный java-код, вы увидите, что методы полностью идентичны. Это потому, что ключевое слово inline - это инструкция компилятору скопировать код на сайт вызова.
Однако, если мы передаем любой тип функции другой функции, как показано ниже:
//Compile time error… Illegal usage of inline function type ftOne...
inline fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
//passing a function type to another function
val funOne = someFunction(ftOne)
/*...*/
}
Чтобы решить эту проблему, мы можем переписать нашу функцию, как показано ниже:
inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
//passing a function type to another function
val funOne = someFunction(ftOne)
/*...*/}
Предположим, у нас есть функция более высокого порядка, как показано ниже:
inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int) {
//passing a function type to another function
val funOne = someFunction(ftOne)
/*...*/}
Здесь компилятор скажет нам не использовать ключевое слово inline, когда есть только один лямбда-параметр, и мы передаем его другой функции. Итак, мы можем переписать вышеуказанную функцию, как показано ниже:
fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int) {
//passing a function type to another function
val funOne = someFunction(ftOne)
/*...*/
}
Примечание : нам также пришлось удалить ключевое слово noinline, потому что оно может использоваться только для встроенных функций!
Предположим, у нас есть такая функция ->
fun intercept() {
// ...
val start = SystemClock.elapsedRealtime()
val result = doSomethingWeWantToMeasure()
val duration = SystemClock.elapsedRealtime() - start
log(duration)
// ...}
Это работает нормально, но основа логики функции загрязнена кодом измерения, что затрудняет вашим коллегам работу над тем, что происходит.:)
Вот как встроенная функция может помочь этому коду:
fun intercept() {
// ...
val result = measure { doSomethingWeWantToMeasure() }
// ...
}
}
inline fun <T> measure(action: () -> T) {
val start = SystemClock.elapsedRealtime()
val result = action()
val duration = SystemClock.elapsedRealtime() - start
log(duration)
return result
}
Теперь я могу сконцентрироваться на чтении основного предназначения функции intercept (), не пропуская строки кода измерения. Нам также выгодна возможность повторного использования этого кода в других местах, где мы хотим
inline позволяет вызывать функцию с лямбда-аргументом внутри замыкания ({...}) вместо передачи лямбда-подобной меры (myLamda)
Когда это полезно?
Ключевое слово inline полезно для функций, которые принимают другие функции или лямбда-выражения в качестве аргументов.
Без ключевого слова inline в функции лямбда-аргумент этой функции преобразуется во время компиляции в экземпляр интерфейса функции с помощью одного метода invoke (), а код в лямбда-выражении выполняется путем вызова invoke () для этого экземпляра функции. внутри тела функции.
С ключевым словом inline в функции такое преобразование времени компиляции никогда не происходит. Вместо этого тело встроенной функции вставляется в ее сайт вызова, и ее код выполняется без накладных расходов на создание экземпляра функции.
Хммм? Пример в android ->
Допустим, у нас есть функция в классе маршрутизатора активности для запуска действия и применения некоторых дополнительных функций.
fun startActivity(context: Context,
activity: Class<*>,
applyExtras: (intent: Intent) -> Unit) {
val intent = Intent(context, activity)
applyExtras(intent)
context.startActivity(intent)
}
Эта функция создает намерение, применяет некоторые дополнения, вызывая аргумент функции applyExtras, и запускает действие.
Если мы посмотрим на скомпилированный байт-код и декомпилируем его в Java, это будет выглядеть примерно так:
void startActivity(Context context,
Class activity,
Function1 applyExtras) {
Intent intent = new Intent(context, activity);
applyExtras.invoke(intent);
context.startActivity(intent);
}
Допустим, мы вызываем это из прослушивателя кликов в действии:
override fun onClick(v: View) {
router.startActivity(this, SomeActivity::class.java) { intent ->
intent.putExtra("key1", "value1")
intent.putExtra("key2", 5)
}
}
Декомпилированный байт-код для этого прослушивателя кликов будет выглядеть примерно так:
@Override void onClick(View v) {
router.startActivity(this, SomeActivity.class, new Function1() {
@Override void invoke(Intent intent) {
intent.putExtra("key1", "value1");
intent.putExtra("key2", 5);
}
}
}
Новый экземпляр Function1 создается каждый раз, когда запускается прослушиватель кликов. Это нормально работает, но не идеально!
Теперь давайте просто добавим inline к нашему методу маршрутизатора активности:
inline fun startActivity(context: Context,
activity: Class<*>,
applyExtras: (intent: Intent) -> Unit) {
val intent = Intent(context, activity)
applyExtras(intent)
context.startActivity(intent)
}
Не меняя вообще код прослушивателя кликов, теперь мы можем избежать создания этого экземпляра Function1. Эквивалент Java-кода прослушивателя кликов теперь будет выглядеть примерно так:
@Override void onClick(View v) {
Intent intent = new Intent(context, SomeActivity.class);
intent.putExtra("key1", "value1");
intent.putExtra("key2", 5);
context.startActivity(intent);
}
Это оно.. :)
«Встроить» функцию в основном означает скопировать тело функции и вставить его в место вызова функции. Это происходит во время компиляции.