oynix

于无声处听惊雷,于无色处见繁花

Kotlin的inline,noinline,crossline

好好说说这line三兄弟。

1. Kotlin高阶函数

要想说明白,从头捋了一下,还得从Kotlin的高阶函数说起。首先,什么是高阶函数呢?其实所谓的高,是相对而言的,就普遍性而言,函数的参数和返回值,要么没有,要么是个数值,要么是个引用类型的对象,这是低阶函数。当一个函数的参数或者返回值类型,也是一个函数类型时,这种就叫高阶函数,函数型的类型,是Kotlin中特有而Java没有的。

1
2
3
4
5
6
7
8
9
10
11
// 这是参数类型是函数的函数
fun fun1(item: () -> Unit) {}

// 这是返回值类型是函数的函数
fun fun2(): () -> Unit {}

// 声明函数类型的变量,
// 因为函数名字用不到,所以被Kotlin强制要求不能写,f1、f2、f3是变量名字
val f1 = fun () {}
val f2: (Int) -> Unit = {}
val f3 = {}

简单吧,高阶函数就是这样的

2. Kotlin中的::

使用两个冒号加上函数的名字,这个函数就可以被当作参数传递,这其中是为什么呢?

首先需要明确的是,函数就是函数,不是对象,然而只有对象才能被当作参数传递,所有传递函数的地方,在编译之后Kotlin都将其封装成了一个对象,通过括号调用函数类型的参数,实际上就是在调用这个函数对象的invoke方法,开发中可以直接传递函数,这本质上是个语法糖。

语法糖,一时爽。我们都知道,创建对象是有消耗的,每调用一次传递函数类型的函数,就要为这个参数创建一个对象,如果在循环中调用,那可能就是个隐藏的内存炸弹。所以,为了解决这个可能的隐患,有请inline登场。

3. inline

inline多用于修饰带有函数类型参数的函数,如果你用它来修饰一个普通函数,也可以,但是IDE会给你弹提醒,告诉你,没必要。调用被inline修饰的函数,不会增加一层调用栈,inline,顾名思义,在一条线上。所以,在编译期间,会把inline的函数直接复制到调用的位置,连同函数类型的参数也一并展开,这样就巧妙的去掉了函数参数,从而也不会增加创建函数对象的消耗了

1
2
3
4
5
6
7
8
9
10
inline fun f(fp: (String) -> Unit) {
println("welcome")
fp("message from f")
}

fun main() {
f { msg ->
println(msg)
}
}

编译后的main函数,会把f的代码复制过,大概长这个样子

1
2
3
4
fun main() {
println("welcome")
println("message from f")
}

4. noinline

看名字就知道,和inline相反,但是inline是修饰方法,noinline是修饰函数参数的,我们已经知道了,inline修饰的方法,在编译时会复制代码,并把函数参数展开。有时候,函数会有多个函数类型的参数,我们并不希望将所有参数都展开,这个时候用noinline修饰不想被展开的函数类型的参数,即可。

5. crossline

这里主要涉及return的问题,看个例子

1
2
3
4
5
6
7
8
9
10
11
12
inline fun hi(callback: () -> Unit) {
println("finish")
callback()
}

fun main() {
hi {
println("callback")
return
}
printlin("main end")
}

调用hi函数时,函数参数体中的return直观上看返回的是hi,然后会执行println(“main end”),但是,hi可是inline修饰的函数,还是先看看最终形态

1
2
3
4
5
6
fun main() {
println("finish")
println("callback")
return
println("main end")
}

这么一看,return又是返回的main函数,所以由于嵌套的函数体,return成了一个繁琐的问题,因此,Kotlin有有几个规定

  • 函数参数的参数体内,不允许调用return,只有inline修饰的函数的函数参数体可以调用return,此时return返回的是外层函数
  • inline修饰的函数中,不允许再嵌套调用函数参数
    1
    2
    3
    4
    5
    6
    inline fun hi(callback: () -> Unit) {
    println("finish")
    scope.launch {
    callback() // 此处报错
    }
    }
  • 如果真的需要在inline函数中嵌套调用函数参数,那么就用crossline修饰这个参数,但是,被修饰参数的函数体中,便不能再调用return
  • 上面只是说不可以单独调用return,但是都可以调用return@label来手动指定返回锚点

总结下来就是,inline函数中没有被crossline修饰的函数参数体中,可以调用return,返回的是外层函数。同时,只有被crossline修饰的函数参数,才可以嵌套调用。

6. 总结

  • inline:修饰函数,被修饰的函数编译后将代码复制到调用处,并将函数类型的参数展开
  • noinline:修饰函数类型的参数,被修饰后将不再展开
  • crossline:修饰函数类型的参数,被修饰后可以在inline函数中嵌套调用
------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2022/04/db78d37c58ab/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道