-
kotlin内联函数是什么?
Kotlin里使用关键字 inline 来表示内联函数。其原理就是:在编译时期,把调用这个函数的地方用这个函数的方法体和参数进行替换。
看一段高阶函数代码:
/** * 高阶函数 */
fun action(block: () -> Unit) {
println("hello")
block()
}
fun main() {
//调用处
action{
println("world")
}
}
实际上,kotlin在java平台上将其包装成匿名内部类的像是调用的,创建了临时对象。大概如下所示:
fun action(block: () -> Unit) {
println("hello")
block()
}
fun main() {
//调用处
val action = object :Function0<Unit>{
override fun invoke() {
return println("world")
}
}
action(action)
}
这个创建的临时变量,就是这个object: Function0这个变量,他是为了兼容java,对不同高阶函数的参数个数匹配的不同的Function0/Function1...Function6等类型。
如果该方法在循环中被调用,那是对性能有相当大的隐患的。
参数内联——优化Lambda开销
函数加上inline,不仅会把函数体内联过来,同时会把参数也内联过来。
比如说我们要计算 println("hello") 这段代码执行的时间,如果不加 inline 的话,那么调用 cost 就要创建 Lambda 表达式,作为Lambda的调用方法,每次调用的时候,都会去创建内部类对象,会有性能损耗。可想而知,Lambda虽然简洁,但是会增加额外的开销。
inline:可以内联函数的函数,可优化调用栈(忽略不计)、优化高阶函数(block)避免创建大量的对象。
private inline fun proxy(action:() -> Unit) {
println("start log")
action() //实际会调用 action.invoke,即会调用闭包里面的代码
println("end log")
}
将函数加上inline之后,调用高阶函数变成了:
fun useInline() {
println("start log")
println("action invoke")
println("end log")
}
会直接把高阶函数的函数体拷贝过来,这样没有了lambda,Lambda 表达式的创建开销也没了。这就是内联函数的作用,主要是对高阶函数的性能优化。
kotlin中的不少高阶函数,扩展函数都使用了lambda作为参数,都用了inline来修饰,标识为内联函数。
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
noinline :局部关闭内联
如果一个函数的开头加上inline修饰符,那么它的函数体以及高阶函数都会被内联。
因为高阶函数也是对象,那么这个高阶函数对象也可以继续被使用,比如被其他高阶函数作为参数使用或者当做返回对象使用,这个时候内联的参数就会报错。通过noinline关闭内联可以让高阶函数继续当做对象来使用。
示例:
private fun clearResource(excution:() -> Unit) {
excution()
println("clean resource1")
println("clean resource2")
}
private inline fun proxy(action: () -> Unit, action2:() -> Unit) {
println("start loggin")
action()
println("end loggin")
clearResource(action2)
}
示例二:
inline fun action2(block: () -> Unit, blockReturn: () -> Unit): () -> Unit {
block()
blockReturn()
return blockReturn
}
/** * 实际编译的代码,如果能编译通过的话(其实用inline修饰后,再将函数作为返回值使编译不通过了,需要加noinline关键字的) */
fun main() {
//调用处
println("hello")
println("world")
blockReturn // ????
}
报错提示:
翻译:
在“private final inline fun proxy(action:()->Unit,action2:()->Unit):com.example.lambdause.InlineStudy中定义的Unit”中非法使用内联参数“action2”。在参数声明中添加“noinline”修饰符。
因为我们把多个参数都内联了,本来想将action2作为高阶函数的参数的,但action2被内联了,被展开成了一块代码,一个代码块无法作为另一个高阶函数的参数。这时用oninline关键字可以去掉部分高阶函数参数的内联。
修改一下添加noinline即可正确编译:
/**
* 高阶函数作为另一个高阶函数参数,参数需要加 oninline,避免内联
*/
private inline fun proxy(action: () -> Unit, noinline action2:() -> Unit) {
println("start loggin")
action()
println("end loggin")
clearResource(action2)
}
/**
* 高阶函数作为返回值,参数需要加noinline
*/
private inline fun proxy2(action: () -> Unit, noinline action2:() -> Unit): () -> Unit {
println("start loggin")
action()
println("end loggin")
return action2
}
private fun clearResource(excution:() -> Unit) {
excution()
println("clean resource1")
println("clean resource2")
}
另:编译器会自动察觉到该场景,如果出现会错误提示,所以什么时候用noinline呢?只需要在idea出现错误提示的时候加上就可以了!
高阶函数中的返回
payFoo2函数没有使用inline的情况下,高阶函数内不允许用return。
fun payFoo2(returning: () -> Unit) {
println("before local return")
returning()
println("after local return")
}
fun main() {
payFoo2 { return } //这里再写return就会报错,不允许这样将payFoo退出。
println("main finish")
}
因为官方规定,普通函数中的lambda表达式里不允许有return,除非是lambda是内联函数的参数。
解决办法一,使用局部返回payFoo2 { return@payFoo2 }的形式。
打印:
before local return
after local return
main finish
局部返回的写法只会影响高阶函数的返回,不会把外部的main方法进行返回。
解决办法一,给payFoo2函数加上内联,看以下示例:
在lambda表达式中调用了return,看return返回了局部函数还是外层的main函数呢?
inline fun payFoo2(returning: () -> Unit) {
println("before local return")
returning()
println("after local return")
}
fun main() {
payFoo2 { return } //这里再写return就会报错,不允许这样将payFoo推出。
println("main finish")
}
打印:
before local return
after local return和main finish都没有打印。事实上,return把局部和外部函数都返回了。
因为内联函数将payFoo2调用的时候进行了铺平,整个main函数成了如下函数,return当然会把main返回了。
即内联函数的lambda表达式中的return不是返回的局部,而是返回的外层函数。
/**
* 使用inline 铺平之后的main函数
*/
fun main() {
println("before local return")
return
println("after local return")
println("main finish")
}
crossinline 局部加强内联优化
如果在高阶函数的调用中增加了一层,如下所示:
inline fun action2(block: () -> Unit) {
println("hello")
middleLayer{
block()
}
}
fun middleLayer(block: () -> Unit){
block()
}
fun main() {
//调用处
action2 {
print("world")
return
}
}
那这个时候crossinline就出现了。corssinline局部加强内联优化,让内联函数里的函数类型的参数可以被间接调用。
那此时return返回的是那一层呢???蒙圈了。。。所以kotlin简单粗暴,就不允许这样的骚操作,直接给你编译不通过了。处理间接调用 return的问题,加上corssinline,直接告知被调用者,此处不允许直接使用return。
那就是还想要用return怎么办?只能return最内层的lambda表达式,使用局部返回的方式:
fun main() {
//调用处
action2 {
print("world")
return@action2 //这样写才能通过,需要加局部返回,只允许在自己范围内去return
}
}
总结:
inline:通过内联(函数内容直插到调用处)的方式来编译函数,推荐高阶函数要使用inline进行优化
noinline:局部关掉这个优化,来摆脱(不能把函数类型的参数当对象使用的使用)限制,比如当另一个高阶函数的函数,当做函数返回值的场景
crossinline:让内联函数里的函数类型的参数可以被间接调用,代价是不能在Lambda表达式里使用return(return@label 这种形式还是可以的)
问题:
在foreach函数中做return局部返回会怎么样
fun main() {
listOf<Int>(1,2,3,4).forEach {
println("---$it")
if (it == 3) {
return
}
}
}
打印:
---1
---2
---3
将forEach这个函数后续执行中断了。
参考:
https://blog.csdn.net/zhangying1994/article/details/104712148
https://www.dandelioncloud.cn/article/details/1578708549574553602
https://www.bilibili.com/video/BV1kz4y1f7sf/