多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 内联函数 使用[高阶函数](lambdas.html)在运行时也会带来确定的不利因素:每个函数都是一个对象,而且它捕获一个闭包,意即那些变量在函数主体内部被访问。运行时会带来额外的内存分配(对函数对象和类两者)和有效调用。 但在一些场景看来这种额外开销可以通过内联 lambda 表达式避免。前文所示的函数是这种场景很好的例子。即,`lock()` 函数可以简单地内联在调用场所。思考下面的情况: ``` kotlin lock(l) { foo() } ``` 代替为参数创建函数对象并生成调用,编译器能够生成如下代码 ``` kotlin l.lock() try { foo() } finally { l.unlock() } ``` 这不是我们从一开始就想要的吗? 要让编译器做到这个,我们需要和 `inline` 修饰符一起来标记 `lock()` 函数: ``` kotlin inline fun lock<T>(lock: Lock, body: () -> T): T { // ... } ``` `inline` 标识符影响函数本身和传递给它的 lambda:它们全部都会被内联到调用场所中。 内联会使得生成的代码增加,但如果我们采用合理的方式(不要内联大的函数)它将在性能上有所回报,尤其是在循环内的 “megamorphic(超级形态??)” 的调用场景。 ## noinline 万一你只要传递到内联函数的 lambda 其中的一些被内联,你可以用 `noinline` 修饰符标记你的函数参数其中的这些: ``` kotlin inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { // ... } ``` 可内联的 lambda 只能在内联函数内部调用或作为可内联的实参传递,但 `noinline` 的能够以我们喜欢的任何方式操作:存储在字段中、在周围传递等等。 注意如果一个内联函数没有可内联的函数参数并且没有[具体化类型的参数](#reified-type-parameters),编译器将发出一个警告,说明内联这样的函数没有好处(如果你确定需要这样内联,你可以禁止这个警告)。 ## 非局部返回 Kotlin 中我们只能使用一个普通、无限制的 `return` 来退出一个已命名的函数或匿名函数。这个意思是指要退出一个 lambda,我们就得使用一个[标签](returns.html#return-at-labels),而且在 lambda 里面禁止裸露的 `return`,因为一个 lambda 不能让封闭的函数返回: ``` kotlin fun foo() { ordinaryFunction { return // 错误:不能让 foo 从这返回 } } ``` 但如果这个函数传递过来的 lambda 已被内联,那么返回就能很好地被内联。因此它允许这样: ``` kotlin fun foo() { inlineFunction { return // OK: lambda 已经内联 } } ``` 这样的返回(位于 lambda 里面,但退出封闭的函数)被称作*非局部*返回。我们经常用内联函数嵌入这种循环构造: ``` kotlin fun hasZeros(ints: List<Int>): Boolean { ints.forEach { if (it == 0) return true // 从 hasZeros 返回 } return false } ``` 注意一些内联函数可以从其它执行上下文像参数一样访问传递给它们的 lambda 而不是直接从函数主体调用,就像一个局部对象或嵌入函数一样。对于这种情况,lambda 内非局部控制流仍然不被允许。要指出这一点,lambda 参数需要以 `crossinline` 修饰符标记: ``` kotlin inline fun f(crossinline body: () -> Unit) { val f = object: Runnable { override fun run() = body() } // ... } ``` > `break` 和 `continue` 在内联 lambda 中不再有效,但我们也有计划支持它们 ## 具体化类型参数 有时我们需要方法传递一个类型作为参数给我们: ``` kotlin fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? { var p = parent while (p != null && !clazz.isInstance(p)) { p = p?.parent } @Suppress("UNCHECKED_CAST") return p as T } ``` 这里,我们沿着一颗树并使用返回来检查一个节点是否有确定的类型。其它的都很好,只是调用场所不太美观: ``` kotlin myTree.findParentOfType(MyTreeNodeType::class.java) ``` 事实我们想要简单地传递一个类型到这个函数,意即像这样调用它: ``` kotlin myTree.findParentOfType<MyTreeNodeType>() ``` 为了能这样,内联函数支持*具体化类型参数*,因此我们能够写下像这样的东西: ``` kotlin inline fun <reified T> TreeNode.findParentOfType(): T? { var p = parent while (p != null && p !is T) { p = p?.parent } return p as T } ``` 我们用 `reified` 修饰符限制这个类型参数,现在它在函数内部的访问差不多与一个普通类一样。只要函数是内联的就不需要反射,普通的操作符,如 `!is` 和 `as` 现在都可以工作。还有,我们可以像上面提到的那样调用它:`myTree.findParentOfType<MyTreeNodeType>()`。 尽管反射在一些情况下并不被需要,我们仍然能够与一个具体化类型参数一起使用它: ``` kotlin inline fun <reified T> membersOf() = T::class.members fun main(s: Array<String>) { println(membersOf<StringBuilder>().joinToString("\n")) } ``` 普通函数(没有被标记为 `inline`)不能有具体化的参数。一个类型没有一个运行时代理(例如,一个非具体化类型参数或一个类似 `Nothing` 的假想类型)不能像一个具体化类型参数的实参那样使用。 更核心的描述查看[特别文档](https://github.com/JetBrains/kotlin/blob/master/spec-docs/reified-type-parameters.md)。