减少调用,将代码直接粘贴在调用的地方,用空间换时间,乱用会导致体积增大。
一、常量的内联(const val 编译时常量)
编译时常量会被编译器以内联的方式进行编译(直接用值替换掉调用处的变量名进行编译),使程序结构简单,方便编译器和JVM做优化。
二、inline 减少Lambda匿名对象创建和函数多次调用的性能开销
被 inline 修饰过的函数在编译时,它的函数体代码和形参 Lambda 的代码会直接替换到调用处:
- 减少匿名对象创建:高阶函数的形参会传入Lambda,Lambda只是一个便捷写法的语法糖,依旧会被编译成接口并通过创建匿名对象来实现,而创建对象就意味着内存开销,尤其是多处调用或循环中大量使用。(如果内部使用(捕获)了外部变量,那么每次调用依然会创建新的匿名对象)
- 减少方法调用次数:函数从开始调用到执行完毕的过程,对应着一个栈帧在虚拟机里从入栈到出栈的过程(方法栈帧的生成、参数字段压入、栈帧弹出、指令执行地址跳转),多一次调用就意味着出入栈开销。
- 针对代码少的高阶函数使用:普通函数在多个地方调用固然内联会提升效率,但是JVM本身就有优化功能,手动内联的优化效果小到可以被忽略的程度。对于代码多的函数内联容易导致多处粘贴让字节码膨胀造成负优化(实在要用尽量把非必要代码抽离出去)。
- 对于 pubnlic 或 protected 声明的内联函数,不可以调用 private 或 internal 声明的成员。因为代码会被复制过去,而调用方肯定是看不到这个私有的内容。但对于 internal 修饰的成员使用 @PublishedApi 注解修饰后,就可以被调用。
inline fun aa(name: String, eat: () -> Unit){...}
二、noinline 解决Lambda形参传递的限制
内联函数的 Lambda 形参只能被直接调用或传递给另一个内联函数,因为内联后 Lambda 形参就不是对象调用了(变成了函数体中的内容),无法用作调用其它非内联的函数的形参传入,除非使用 noinline 修饰。(不用刻意去想什么时候加上,写代码触发了该类场景 IDE 会给出提示)。
//内联函数aa中的函数参数eat使用noinline修饰后才能在方法体中传递给非内联函数bb
inline fun aa(name: String, noinline eat: () -> Unit){
bb(eat)
}
fun bb(drink: ()-> Unit){}
三、crossinline 解决间接调用和使用return的问题
- Lambda 不允许直接使用 return(可以使用带标签的return),除非作为内联函数的形参编写时。由于 Lambda 没有使用 fun 关键字,使用 return 会结束直接包裹它的外层函数,由于包裹它的内联函数也会被替换成具体代码,因此真正被结束掉的是调用内联函数的作用域。
- 内联函数中的 Lambda 形参不允许被间接调用(在内联函数中不是直接调用该形参,而是在某个函数内调用的它),因为这时它的意义就不是结束掉调用内联函数的作用域了,而是这个外层包裹它的函数,这显然会造成混乱。
- 使用 crossinline 修饰内联函数的 Lambda 形参后,就可以被间接调用了,但必须使用带标签的 return(局部返回),这样就不会影响内联函数中 Lambda 之后的代码执行。(一样不用刻意去想什么时候加上,写代码触发了该类场景IDE会给出提示)。
inline fun aa(name: String, crossinline eat: () -> Unit){
println("1")
//aa间接调用Lambda形参eat()
runOnUiThread(){
eat()
}
println("2")
}
//使用局部返回,不影响Lambda之后的执行流程,即上面eat()之后的代码
aa("张三"){
return@aa
}
fun test() {
innerFun {
//return 这样写会报错,非局部返回,直接退出 test() 函数。
return@innerFun //局部返回,退出 innerFun() 函数,这里必须明确退出哪个函数,写成 return@test 则会退出 test() 函数
}
//以下代码依旧会执行
println("test...")
}
fun innerFun(a: () -> Unit) {
a()
}
四、reified 具体化的类型参数
内联函数会在调用时直接将函数体中的代码复制过去,由于引用的是具体代码,调用函数时指定的 <T> 不会被运行时发生的类型擦出影响。因此用 reified 修饰了 T 后,T 就能被当做一个类型使用,还能获取到反射类型的 Class 对象。
//T无法用来检查类型
fun <T> demo2(param:Any){
if(param is T){}
}
//使用 reified 关键字便可以是否是T类型,但只能用在 inline 函数上
inline fun <reified T> demo3(param:Any){
if(param is T)
}
inline fun <reified T : Activity> (context: Context)
= startActivity(Intent(context, T::))
startActivity<LoginActivity>(context)
五、偏门的另类用法
Kotlin 源码通过使用 inline 内联 Java 的静态函数, 从而去掉 Java 静态方法的前缀让调用更简单。
import as nativeMath
public actual inline fun min(a:Int, b:Int): Int = (a, b)