-
kotlin内联函数是什么?
Kotlin里使用关键字 inline 来表示内联函数。其原理就是:在编译时期,把调用这个函数的地方用这个函数的方法体进行替换。
Java 方法执行的内存模型是基于 Java 虚拟机栈的:每个方法被执行的时候都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧入栈、出栈的过程。
也就是说每调用一个方法,都会对应一个栈帧的入栈出栈过程,如果你有一个工具类方法,在某个循环里调用很多次,那就会对应很多次的栈帧入栈、出栈过程。栈帧的创建及入栈、出栈都是有性能损耗的。
比如test方法中,重复调用多次sum方法:
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
}
为了避免多次调用 sum() 方法带来的性能损耗,最好改为调用一次方法,将方法内的代码插入到调用该方法的地方:
fun test2() {
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)
}
用关键字 inline 标记函数,该函数就是一个内联函数。还是原来的 test() 方法,编译器在编译的时候,会自动把内联函数 sum() 方法体内的代码,替换到调用该方法的地方。查看编译后的字节码,会发现 test() 方法里已经没了对 sum() 方法的调用,凡是原来代码里出现 sum() 方法调用的地方,出现的都是 sum() 方法体内的字节码了。
将sum方法加入inline声明,即可实现test2方法同等的效率。
inline fun sum(vararg ints: Int): Int {
var sum = 0
for (i in ints) {
sum += i
}
return sum
}
内联函数通常与高阶函数搭配使用——优化Lambda开销
在Kotlin中每次声明一个Lambda表达式,就会在字节码中产生一个匿名类。该匿名类包含了一个invoke方法,作为Lambda的调用方法,每次调用的时候,还会创建一个新的对象。可想而知,Lambda虽然简洁,但是会增加额外的开销。Kotlin 采用内联函数来优化Lambda带来的额外开销。
cost {
println("hello")
}
inline fun cost(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
println(System.currentTimeMillis() - start)
}
比如说我们要计算 println("hello") 这段代码执行的时间,如果不加 inline 的话,那么调用 cost 就要创建 Lambda 表达式,函数调用,结果发现这些操作的时间就要大于 println("hello") 这段代码执行的时间,这样是完全没有必要的,所以我们加上 inline 之后,调用 println("hello"),实际上就等于下面这样:
val start = System.currentTimeMillis()
println("hello")
println(System.currentTimeMillis() - start)
加上 inline 之后,cost 函数的调用开销没了,Lambda 表达式的创建开销也没了,这就是内联函数的作用,主要是对高阶函数的性能优化。
kotlin中的不少高阶函数,扩展函数都用了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 :避免参数被内联
如果一个内联函数的参数里包含 lambda表达式,也就是函数参数,那么该形参也是 inline 的,举个例子:
inline fun test(inlined: () -> Unit) {...}
Kotlin引入了noinline关键字,可以加在不想要内联的参数开头,该参数便不会有内联效果。
fun main() {
payFoo({
println("I am inlined...")
}, {
println("I am not inlined...")
})
}
inline fun payFoo(block1: () -> Unit, noinline block2: () -> Unit) {
println("before block")
block1()
block2()
println("end block")
}
网友评论