Kotlin的inline、noinline、crossinline全面分析

1 说起内联函数,就是在方法名前加个inline关键字。在Kotlin开发随处可见,例如在Kotlin标准函数中。

kotlin 复制代码
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#with).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#also).
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

在说内联函数之前,先说其他的概念就是Kotlin的高阶函数其实是实现FunctionN接口的一个实例。Kotlin的lambda作为实参传给参数时,也会创建匿名内部类以及调用invoke方法。下面以简单代码表述一下。

kotlin 复制代码
//在Kt文件中定义一个高阶函数
private fun lambdaFun(action: () -> Unit) {
    println("[lambdaFun]: 执行前")
    action()
    println("[lambdaFun]: 执行后")
}

private fun testLambdaFun() {
    lambdaFun {
        println("[testLambdaFun]: 正在执行")
    }
}

下面是这段代码的字节码

arduino 复制代码
private final void lambdaFun(Function0 action) {
   String var2 = "[lambdaFun]: 执行前";
   System.out.println(var2);
   action.invoke();
   var2 = "[lambdaFun]: 执行后";
   System.out.println(var2);
}

private final void testLambdaFun() {
   this.lambdaFun((Function0)null.INSTANCE);
}

这里发现了吧lambda转换成匿名内部类,如果此时频繁多次调用的话,就会导致内存增加,这也就是inline关键字出现的原因。

但当我们把lambdaFun函数加上inline修饰时

ini 复制代码
private final void lambdaFun(Function0 action) {
   int $i$f$lambdaFun = false;
   String var3 = "[lambdaFun]: 执行前";
   System.out.println(var3);
   action.invoke();
   var3 = "[lambdaFun]: 执行后";
   System.out.println(var3);
}

private final void testLambdaFun() {
   int $i$f$lambdaFun = false;
   String var3 = "[lambdaFun]: 执行前";
   System.out.println(var3);
   int var4 = false;
   String var5 = "[testLambdaFun]: 正在执行";
   System.out.println(var5);
   var3 = "[lambdaFun]: 执行后";
   System.out.println(var3);
}

可以看到当函数是内联函数时,函数内部的函数体以及函数类型参数都会被内联到调用地方(也就是把函数体的代码和函数类型参数给赋值铺平到调用地方)。它解决了使用lambad方便的同时创建过多的匿名内部类,减小内存的使用。

noline 这个关键字只能修饰函数的参数,而inline是用来修饰函数的,也就是函数是内联的,但是这个函数类型参数不是内联的。

kotlin 复制代码
/**
 * 定义一个高阶函数,非内联
 */
private  fun lambdaFun(action: () -> Unit) {
    println("[lambdaFun]: 执行前")
    action()
    println("[lambdaFun]: 执行后")
}


/**
 * 测试函数
 */
private inline fun testLambdaFun(action: () -> Unit) {
    println("[testLambdaFun]: 执行前")
    action()
    lambdaFun(action)
    println("[testLambdaFun]: 执行后")
}

在内联函数testLambdaFun中调用lambdaFun高阶函数,想把action传给lambadFun,但是在被inline修饰的函数,其参数也会被铺平,也就不会再是函数类型了。所以编译报错了。所有这里的action要使用noinline来修饰。

下面我们说下return问题。

在测试函数中调用高阶函数lambdaFun,看到这里使用return语句,提醒 return@lambdaFun,如果我只使用return的话,那么就需要把lambdaFun定义成内联函数。

kotlin 复制代码
/**
 * 定义一个高阶函数,非内联
 */
private inline fun lambdaFun(action: () -> Unit) {
    println("[lambdaFun]: 执行前")
    action()
    println("[lambdaFun]: 执行后")
}


/**
 * 测试函数
 */
private fun testLambdaFun() {
    lambdaFun {
        println("[testLambdaFun]: 调用中")
        return 1处
    }
}

1处这里居然直接不报错了,由于内联函数会把函数体和lambad给复制铺平到调用地方,所以这里的return必然是testLambad函数了。

Kotlin直接规定,为了解决lambad表达式中的return语句问题,在非inline函数中,return无法使用(必须return@xx显式指明返回的函数),只有在inline函数中可以使用return,根据inline的特性,这个return必然是返回调用者的函数。

下面我们讲crossinline

kotlin 复制代码
/**
 * 定义一个高阶函数,非内联
 */
private fun lambdaFun(action: () -> Unit) {
    println("[lambdaFun]: 执行前")
    action()
    println("[lambdaFun]: 执行后")
}


/**
 * 测试函数
 */
private inline fun testLambdaFun(action: () -> Unit) {
    lambdaFun { 2处
        action() 
    }
}

2处这里我们使用lambda表达式,在表达式内调用action()也就是其invoke函数,而不是把action这个函数类型参数进行传递,

可以看到我们其实有两种方式调用lambda高阶函数的。

返回到上处地方报错。

报错信息意思:无法内联action参数,原因是它可能包含return语句。

原因看起来也简单,在lambda表达式中,只有在内联函数中才能使用return。

kotlin 复制代码
/**
 * 测试函数
 */ 3处
private inline fun testLambdaFun(action: () -> Unit) {
    lambdaFun { 4处
        action()
    }
}

3处和4处满足了是inline函数和在表达式中,而且无法确定action中会不会包含return语句。inline受限严重,这里只有突破这个限制才可以,所以这里采用的方式就是把action加个crossinline修饰符。

crossinle的作用仅仅是当被这个修饰符的参数会告诉IDE来检查你的代码中没有return。

相关推荐
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker2 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95272 天前
Andorid Google 登录接入文档
android
黄林晴2 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android