inline函数(内联函数)从概念上讲是编译器使用函数实现的真实代码来替换每一次的函数调用,带来的最直接的好处就是节省了函数调用的开销,而缺点就是增加了所生成字节码的尺寸。基于此,在代码量不是很大的情况下,我们是否有必要将所有的函数定义为内联?让我们分两种情况进行说明:
- 将普通函数定义为内联:众所周知,JVM内部已经实现了内联优化,它会在任何可以通过内联来提升性能的地方将函数调用内联化,并且相对于手动将普通函数定义为内联,通过JVM内联优化所生成的字节码,每个函数的实现只会出现一次,这样在保证减少运行时开销的同时,也没有增加字节码的尺寸;所以我们可以得出结论,对于普通函数,我们没有必要将其声明为内联函数,而是交给JVM自行优化。
- 将带有lambda参数的函数定义为内联:是的,这种情况下确实可以提高性能;但在使用的过程中,我们会发现它是有诸多限制的,让我们从下面的例子开始展开说明:
inline fun doSomething(action: () -> Unit) {
println("Before doSomething...")
action()
println("After doSomething...")
}
假如我们这样调用doSomething
:
fun main(args: Array<String>) {
doSomething {
pringln("Hello World")
}
}
上面的调用会被编译成:
fun main(args: Array<String>) {
println("Before doSomething...")
println("Hello World")
println("After doSomething...")
}
从上面编译的结果可以看出,无论doSomething
函数还是action
参数都被内联了,很棒,那让我们换一种调用方式:
fun main(args: Array<String>) {
val action:() -> Unit = { println("Hello World") }
doSomething(action)
}
上面的调用会被编译成:
fun main(args: Array<String>) {
println("Before doSomething...")
action()
println("After doSomething...")
}
doSomething
函数被内联,而action
参数没有被内联,这是因为以函数型变量的形式传递给doSomething
的lambda在函数的调用点是不可用的,只有等到doSomething被内联后,该lambda才可以正常使用。
通过上面的例子,我们对lambda表达式何时被内联做一下简单的总结:
- 当lambda表达式以参数的形式直接传递给内联函数,那么lambda表达式的代码会被直接替换到最终生成的代码中。
- 当lambda表达式在某个地方被保存起来,然后以变量形式传递给内联函数,那么此时的lambda表达式的代码将不会被内联。
上面对lambda的内联时机进行了讨论,消化片刻后让我们再看最后一个例子:
inline fun doSomething(action: () -> Unit, secretAction: () -> Unit) {
action()
doSomethingSecret(secretAction)
}
fun doSomethingSecret(secretAction: () -> Unit) {
}
上面的例子是否有问题?是的,编译器会抛出“Illegal usage of inline-parameter”的错误,这是因为Kotlin规定内联函数中的lambda参数只能被直接调用或者传递给另外一个内联函数,除此之外不能作为他用;那我们如果确实想要将某一个lambda传递给一个非内联函数怎么办?我们只需将上述代码这样改造即可:
inline fun doSomething(action: () -> Unit, noinline secretAction: () -> Unit) {
action()
doSomethingSecret(secretAction)
}
fun doSomethingSecret(secretAction: () -> Unit) {
}
很简单,在不需要内联的lambda参数前加上noinline
修饰符就可以了。
以上便是我对内联函数的全部理解,通过掌握该特性的运行机制,相信大家可以做到在正确的时机使用该特性,而非滥用或因恐惧弃而不用。