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。

相关推荐
2501_915921438 小时前
iOS App 电耗管理 通过系统电池记录、Xcode Instruments 与克魔(KeyMob)组合使用
android·ios·小程序·https·uni-app·iphone·webview
June bug9 小时前
【配环境】安卓项目开发环境
android
2501_9445264211 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 蜘蛛纸牌游戏实现
android·java·python·flutter·游戏
csj5012 小时前
安卓基础之《(18)—内容提供者(4)在应用之间共享文件》
android
尤老师FPGA12 小时前
使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第四十五讲)
android·java·ui
北辰当尹13 小时前
xml基础
android·xml
龙之叶14 小时前
【Android Monkey源码解析四】- 异常捕获/页面控制
android·windows·adb·monkey
_F_y15 小时前
MySQL表的操作
android·数据库·mysql
yngsqq16 小时前
AndroidStudio汉化步骤
android
HyEISN17 小时前
Android 9 开启远程adb
android·adb