Kotlin内联函数
Kotlin里使用关键 inline
来表示内联函数,那么到底什么是内联函数呢,内联函数有什么好处呢?
1. 什么是内联inline?
在 Java 里是没有内联这个概念的,所有的函数调用都是普通方法调用,如果了解 Java 虚拟机原理的,可以知道 Java 方法执行的内存模型是基于 Java 虚拟机栈的:每个方法被执行的时候都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧入栈、出栈的过程。
也就是说每调用一个方法,都会对应一个栈帧的入栈出栈过程,如果你有一个工具类方法,在某个循环里调用很多次,那就会对应很多次的栈帧入栈、出栈过程。这里首先要记住的一点是,栈帧的创建及入栈、出栈都是有性能损耗的。下面以一个例子来说明,看段代码片段:
fun test() {
//多次调用 sum() 方法进行求和运算
println(sum(1, 2, 3))
println(sum(100, 200, 300))
println(sum(12, 34))
//....可能还有若干次
}
/**
* 求和计算
*/
fun sum(vararg ints: Int): Int {
var sum = 0
for (i in ints) {
sum += i
}
return sum
}
在测试方法 test() 里,我们多次调用了 sum() 方法。为了避免多次调用 sum() 方法带来的性能损耗,我们期望的代码类似这样子的:
fun test() {
var sum = 0
for (i in arrayOf(1, 2, 3)) {
sum += i
}
println(sum)
sum = 0
for (i in arrayOf(100, 200, 300)) {
sum += i
}
println(sum)
sum = 0
for (i in arrayOf(12, 34)) {
sum += i
}
println(sum)
}
3次数据求和操作,都是在 test() 方法里执行的,没有之前的 sum() 方法调用,最后的结果依然是一样的,但是由于减少了方法调用,虽然代码量增加了,但是性能确提升了。那么怎么实现这种情况呢,一般工具类有很多公共方法,我总不能在需要调用这些公共方法的地方,把代码复制一遍吧,内联就是为了解决这一问题。
定义内联函数:
inline fun sum(vararg ints: Int): Int {
var sum = 0
for (i in ints) {
sum += i
}
return sum
}
如上所示,用关键字 inline
标记函数,该函数就是一个内联函数。还是原来的 test() 方法,编译器在编译的时候,会自动把内联函数 sum() 方法体内的代码,替换到调用该方法的地方。查看编译后的字节码,会发现 test() 方法里已经没了对 sum() 方法的调用,凡是原来代码里出现 sum() 方法调用的地方,出现的都是 sum() 方法体内的字节码了。
2. noinline
如果一个内联函数的参数里包含 lambda表达式
,也就是函数参数,那么该形参也是 inline
的,举个例子:
inline fun test(inlined: () -> Unit) {...}
这里有个问题需要注意,如果在内联函数的内部,函数参数被其他非内联函数调用,就会报错,如下所示:
//内联函数
inline fun test(inlined: () -> Unit) {
//这里会报错
otherNoinlineMethod(inlined)
}
//非内联函数
fun otherNoinlineMethod(oninline: () -> Unit) {
}
要解决这个问题,必须为内联函数的参数加上 noinline
修饰,表示禁止内联,保留原有函数的特性,所以 test() 方法正确的写法应该是:
inline fun test(noinline inlined: () -> Unit) {
otherNoinlineMethod(inlined)
}
3. crossinline
首先来理解一个概念:非局部返回
。我们来举个栗子:
fun test() {
innerFun {
//return 这样写会报错,非局部返回,直接退出 test() 函数。
return@innerFun //局部返回,退出 innerFun() 函数,这里必须明确退出哪个函数,写成 return@test 则会退出 test() 函数
}
//以下代码依旧会执行
println("test...")
}
fun innerFun(a: () -> Unit) {
a()
}
非局部返回我的理解就是返回到顶层函数,如上面代码中所示,默认情况下是不能直接 return 的,但是内联函数确是可以的。所以改成下面这个样子:
fun test() {
innerFun {
return //非局部返回,直接退出 test() 函数。
}
//以下代码不会执行
println("test...")
}
inline fun innerFun(a: () -> Unit) {
a()
}
也就是说内联函数的函数参数在调用时,可以非局部返回,如上所示。那么 crossinline 修饰的 lambda 参数,可以禁止内联函数调用时非局部返回。
fun test() {
innerFun {
return //这里这样会报错,只能 return@innerFun
}
//以下代码不会执行
println("test...")
}
inline fun innerFun(crossinline a: () -> Unit) {
a()
}
4. 具体化的类型参数 reified
这个特性我觉得特别牛逼,有了它可以少些好多代码。在 Java 中是不能直接使用泛型的类型的,但是在 Kotlin 中确可以。举个栗子:
inline fun <reified T: Activity> startActivity() {
startActivity(Intent(this, T::class.java))
}
使用时直接传入泛型即可,代码简洁明了:
startActivity<MainActivity>()
5. 小结
网上很多学习教程对内联函数的讲解都是千篇一律,说实话刚开始很难理解。本文尝试着用最简单的例子,来讲清楚什么是内联函数。在Java中我们一般会有很多工具类、工具方法,在Kotlin中则没有了工具类一说,通常都是将工具方法设计成顶层的内联函数来使用。