Kotlin 三个inline

作者: zcwfeng | 来源:发表于2020-09-04 23:29 被阅读0次

整理借鉴出自 https://mp.weixin.qq.com/s/iOId4kwYe1HCyrS4T-yb8w
做笔记用, 一直理解比较混乱,现在算是清晰了,为了防止找不到这个讲解

inline

Kotlin 里有个特别好用的关键字叫 inline,它可以帮你对做了标记的函数进行内联优化。所谓内联就是,调用的函数在编译的时候会变成代码内嵌的形式:

-> 正常写是这样
fun main() {
    hello()
}
inline fun hello(){
    println("hello")
}
-> 编译后的代码
fun main() {
   println("hello")
}

编译时常量

kotlin 声明 const val AUTHOR = "David"
查看对应的java 代码->
public static final String AUTHOR = "David";

Java 里有个概念叫编译时常量 Compile-time Constant,直观地讲就是这个变量的值是固定不变的,并且编译器在编译的时候就能确定这个变量的值。具体到代码上,就是这个变量需要是 final 的,类型只能是字符串或者基本类型,而且这个变量需要在声明的时候就赋值.
这种编译时常量,会被编译器以内联的形式进行编译,也就是直接把你的值拿过去替换掉调用处的变量名来编译。这样一来,程序结构就变简单了,编译器和 JVM 也方便做各种优化。这,就是编译时常量的作用。

这种编译时常量,到了 Kotlin 里有了一个专有的关键字,叫 const:一个变量如果以 const val 开头,它就会被编译器当做编译时常量来进行内联式编译

让变量内联用的是 const;而除了变量,Kotlin 还增加了对函数进行内联的支持。在 Kotlin 里,你给一个函数加上 inline 关键字,这个函数就会被以内联的方式进行编译。

事实上,inline 关键字不止可以内联自己的内部代码,还可以内联自己内部的内部的代码。什么叫「内部的内部」?就是自己的函数类型的参数。

fun hello(postAction:()->Unit){
    println("hello")
    postAction
}

fun main() {
    hello {
        println("bye")
    }
}

因为 Java 并没有对函数类型的变量的原生支持,Kotlin 需要想办法来让这种自己新引入的概念在 JVM 中落地。而它想的办法是什么呢?就是用一个 JVM 对象来作为函数类型的变量的实际载体,让这个对象去执行实际的代码。也就是说,在我对代码做了刚才那种修改之后,程序在每次调用 hello() 的时候都会创建一个对象来执行 Lambda 表达式里的代码,虽然这个对象是用一下之后马上就被抛弃,但它确实被创建了。

这有什么坏处?其实一般情况下也没什么坏处,多创建个对象算什么?但是你想一下,如果这种函数被放在循环里执行:

fun main() {
    for (i in 1.. 100){
        hello {
            println("bye")
        }
    }
}

内存占用是不是一下就飚起来了?而且关键是,你作为函数的创建者,并不知道、也没法规定别人在什么地方调用这个函数,也就是说,这个函数是否出现在循环或者界面刷新之类的高频场景里,是完全不可控的。

如果我们加上inline 编译后就不是这样

inline fun hello(postAction:()->Unit){
    println("hello")
    postAction()
}

mian -> 会变成这样

fun main() {
    for (i in 1.. 100){
           println("hello")
            println("bye")
       
    }
}

相当于吧嵌套去掉,展评了,减少栈操作和对象创建

noinline

说完 inline,我们来说另一个关键字:noinline。noinline 的意思很直白:inline 是内联,而 noinline 就是不内联。不过它不是作用于函数的,而是作用于函数的参数:对于一个标记了 inline 的内联函数,你可以对它的任何一个或多个函数类型的参数添加 noinline 关键字:

inline fun hello(preAction: () -> Unit, noinline postAction: () -> Unit) {
    preAction()
    println("hello")
    postAction()
}

//添加了之后,这个参数就不会参与内联了:
fun main() {
    hello({
        println("一言难尽")
    }, {
        println("Bye!")
    })
}

// 实际编译
public static final void main() {
      
        println("一言难尽")
        ->inline
        println("hello")
        -> noinline
         ( {
        println("Bye!")
        }).invoke()
   }

如果我们在函数末尾添加返回值return postAction

-> postAction 需要加noinline 
inline fun hello(preAction: () -> Unit, postAction: () -> Unit):()->Unit {
    preAction()
    println("hello")
    postAction()
-> 这里会报错
    return postAction
}

原因,如果不加noinline 那么展开后是这个鬼样子

// 实际编译
public static final void main() {
     
        println("一言难尽")
        println("hello")
        println("Bye!")
        -> 这个时候没人认得这个东西是啥
       postAction
   }

所以,noinline 的作用是什么?是用来局部地、指向性地关掉函数的内联优化的。既然是优化,为什么要关掉?因为这种优化会导致函数中的函数类型的参数无法被当做对象使用,也就是说,这种优化会对 Kotlin 的功能做出一定程度的收窄。而当你需要这个功能的时候,就要手动关闭优化了。这也是 inline 默认是关闭、需要手动开启的另一个原因:它会收窄 Kotlin 的功能。

那么,我们应该怎么判断什么时候用 noinline 呢?很简单,比 inline 还要简单:你不用判断,Android Studio 会告诉你的。当你在内联函数里对函数类型的参数使用了风骚操作,Android Studio 拒绝编译的时候,你再加上 noinline 就可以了。

crossinline

具体细节看文章吧,弄码哥nomag, https://www.jianshu.com/p/cd0be9b887ec

inline 关键字的作用,是把 inline 方法以及方法中的 lambda 参数在编译期间复制到调用方,进而减少函数调用以及对象生成。对于有时候我们不想让 inline 关键字对 lambda 参数产生影响,可以使用 noline 关键字。如果想 lambda 也被 inline,但是不影响调用方的控制流程,那么就要是用 crossinline。

两篇文章结合看,相信完全可以看懂是啥了。

相关文章

网友评论

    本文标题:Kotlin 三个inline

    本文链接:https://www.haomeiwen.com/subject/zzntektx.html