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。

相关推荐
idealzouhu2 小时前
【Android】深入浅出 JNI
android·开发语言·python·jni
2501_944446002 小时前
Flutter&OpenHarmony字体与排版设计
android·javascript·flutter
消失的旧时光-19432 小时前
mixin 写一个 Flutter 的“埋点 + 日志 + 性能监控”完整框架示例
android·flutter
we1less3 小时前
[audio] AudioTrack (五) 共享内存创建分析
android·java·开发语言
hslinux3 小时前
NDK 通过configure 编译C++源码通用脚本
android·c++·ndk·configure
2501_915106323 小时前
iOS 抓包工具有哪些?不同类型的抓包工具可以做什么
android·ios·小程序·https·uni-app·iphone·webview
芦半山4 小时前
2025:生活是个缓慢受锤的过程
android·年终总结
Kapaseker12 小时前
你不看会后悔的2025年终总结
android·kotlin
alexhilton15 小时前
务实的模块化:连接模块(wiring modules)的妙用
android·kotlin·android jetpack