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 Launcher 性能优化完全指南:从启动到滑动,构建极致流畅的桌面体验
android·性能优化
hunterandroid2 小时前
Android ANR 排查实战:从线上告警到主线程卡点定位
android·前端
TimeFine2 小时前
智能眼镜开发:图片翻译与EXIF的重要性
android
律宏阔2 小时前
Android 车载蓝牙开发笔记:HFP、A2DP、AVRCP、PBAP、MAP、BLE 与系统 API
android·蓝牙
@Adzc2 小时前
Android 16 省电模式 Tile 点击导致状态栏收起
android
杉氧2 小时前
打破边界(二):在 React Native 中嵌入原生 UI 组件 (Native UI Components)
android·前端·react native
古法安卓3 小时前
Android-切换白天黑夜内存不足问题排查
android·java·android studio
聚美智数3 小时前
快递拦截-物流在途拦截-在途拦截API接口介绍
android
flower_drop3 小时前
从 QtScrcpy 到 TabQA:如何在浏览器侧边栏搞定 Android 投屏与测试提单?
android
脚踏实地,坚持不懈!4 小时前
Android 系统工程师(性能/功耗/稳定性)岗位问题深度解析:从内核源码到实战排查(完善版)
android·linux·运维·服务器