Kotlin - 内联函数 inline、reified

减少调用,将代码直接粘贴在调用的地方,用空间换时间,乱用会导致体积增大。

一、常量的内联(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> Activity.startActivity(context: Context) 
 = startActivity(Intent(context, T::class.java))

startActivity<LoginActivity>(context)

五、偏门的另类用法

Kotlin 源码通过使用 inline 内联 Java 的静态函数, 从而去掉 Java 静态方法的前缀让调用更简单。

import java.lang.Math as nativeMath

public actual inline fun min(a:Int, b:Int): Int = nativeMath.min(a, b)
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值