💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 前言 **使用高阶函数(higher-order functions)会导致一些性能的损耗:每个函数都是对象,且会捕获闭包closure(即变量会在函数体内被访问),函数对象/类会增加内存分配,而且虚拟调用栈也会增加额外内存开销!** **可用内联函数(inline function)消除这些额外内存开销,说白了就是在调用处插入函数体代码,以此减少新建函数栈和对象的内存开销!被inline修饰的函数或lambda表达式,在调用时都会被内联(在调用处插入函数体代码)**。 [TOC] ## 内联函数 **在Kotlin语法中,Lambda表达式都会被编译成一个匿名类**。这样的话**每次调用Lambda表达式时,就会创建出新的对象,造成额外的内存开销,导致程序效率降低,为了解决这个问题,Kotlin中提供了一个修饰符“inline”,被inline修饰的Lambda(函数)称为内联函数,使用内联函数可以降低程序的内存消耗,`inline`修饰符影响函数本身和传给它的 lambda 表达式:所有这些都将内联到调用处**。说白了**就是在调用处插入函数体代码,以此减少新建函数栈和对象的内存开销!被inline修饰的函数或lambda表达式,在调用时都会被内联(在调用处插入函数体代码)**。 本节将针对Kotlin中的内联函数进行学习。 ### 使用内联函数 在Kotlin中,**被“inline”修饰符修饰的Lambda(函数)称为内联函数**,使用内联函数可以**降低程序的内存消耗,但内联函数要合理使用,不要内联一个复杂功能的函数,尤其在循环中**。接下来让我们通过一个案例来学习内联函数,具体代码如下所示。 ``` import java.util.concurrent.locks.Lock import java.util.concurrent.locks.ReentrantLock inline fun <T> check(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } } fun main(args: Array<String>) { var l = ReentrantLock() check(l, { print("这是内联函数方法体") })//l是一个Lock对象 } ``` 运行结果: ``` 这是内联函数方法体 ``` 在上述代码中,**通过inline关键字指定check()函数就是一个内联函数。在调用内联函数时,编译器会对`check(l, { print("这是内联函数方法体") })`这句代码优化,去掉方法名称以避免入栈出栈操作,直接执行方法体内的内容**。具体代码如下: ``` lock.lock() try { return "这是内联函数方法体" } finally { lock.unlock() } } ``` 通过上述代码可以看出,**内联函数实际上在运行期间会增加代码量,但对程序可读性不会造成影响,同时也提升了性能**。 >[info]**注意**:内联可能导致生成的代码增加;不过如果我们使用得当(即避免内联过大函数),性能上会有所提升,尤其是在循环中的“超多态(megamorphic)”调用处。 ### 禁用内联函数 目前我们已经知道**当使用内联函数时,参数也都会随之内联**,此时便会出现一个问题,**如果内联函数的有些(作为参数)lambda表达式不是内联,可用noinline修饰符函数参数!或者如果希望只内联一部分传给内联函数的 lambda 表达式参数,那么可以用`noinline`修饰符标记不希望内联的函数参数**: ~~~ inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { …… } ~~~ 就是**如果参数有Lambda表达式时,Lambda表达式便不是一个函数的对象(lambda表达式也就不能在调用处直接被插入),从而也就无法当作参数来传递。为了解决这个问题,可以使用noinline修饰符来修饰参数,禁止参数产生内联关系**。 **可以内联的 Lambda 表达式只能在内联函数内部调用或者作为可内联的参数传递, 但是 noinline 的可以以任何我们喜欢的方式操作:存储在字段中、传送它等等**。 接下来我们通过一个案例来演示,具体代码如下所示。 ``` inline fun check(noinline function: (Int) -> Boolean){ test(function) } fun test(function: (Int) -> Boolean){ println("编译通过") } fun main(args: Array<String>) { check { x : Int -> x == 2 } } ``` 运行结果: ``` 编译通过 ``` 从运行结果可以看出,**通过noinline修饰的Lambda表达式是非内联关系,可以当作参数使用**。 >[success]**注意**,如果一个内联函数没有可内联的函数参数并且没有[具体化的类型参数](http://www.kotlincn.net/docs/reference/inline-functions.html#%E5%85%B7%E4%BD%93%E5%8C%96%E7%9A%84%E7%B1%BB%E5%9E%8B%E5%8F%82%E6%95%B0),编译器会产生一个警告,因为内联这样的函数很可能并无益处(如果你确认需要内联,则可以用 @Suppress("NOTHING_TO_INLINE") 注解关掉该警告)。