Kotlin Reference: Inline Functions

  使用高阶函数会引入一定的运行时损耗:每一个函数都是一个对象,并且要获取闭包,即在函数内访问外部函数作用域的变量。(对函数对象和类的)内存分配和虚调用都会引入运行时开销。

  但在很多情况下,使用内联 Lambda 表达式可以消除此种开销,下面给出的函数很好地展现了这一情况,即 lock() 函数可以很容易地在调用处内联,考虑下面的场景:

[code lang=”kotlin”]lock(l) { foo() }[/code]

  编译器不会为该参数创建一个函数对象再进行调用,而是会生成如下的代码:

[code lang=”kotlin”]l.lock()
try {
foo()
}
finally {
l.unlock()
}[/code]

这正是我们一开始想要的结果。

  为了让编译器能实现这样的效果,需要使用 inline 修饰符标记 lock() 函数:

[code lang=”kotlin”]inline fun lock&ltT&gt(lock: Lock, body: () -&gt T): T {
// …
}[/code]

  inline 修饰符会影响函数本身和传递给该函数的 Lambda:它们都会在调用处内联。

  内联会导致生成的代码量变大,但如果合理地利用(不要内联大函数),就会带来性能上的收益,尤其是在循环内的“超级多态(Megamorphic)”调用处。

noinline

  对于传递给内联函数的多个 Lambda 表达式,如果只希望内联其中一部分,可以使用 noinline 修饰符标记不希望内联的函数参数。

[code lang=”kotlin”]inline fun foo(inlined: () -&gt Unit, noinline notInlined: () -&gt Unit) {
// …
}[/code]

  内联 Lambda 只能在内联函数内调用,或作为内联参数传递。而对 noinline 的 Lambda 则可以进行任意操作,如存储为变量并传递到别处。

  注意如果内联函数没有可内联的函数参数,也没有具体化类型参数,则编译器会给出警告,因为内联该函数很可能不会带来收益(如果确定需要内联,可以忽略该警告)。

Non-local returns

  在 Kotlin 中,只能使用普通的、未限定的 return 来退出一个有名称的函数或匿名函数。这意味着如果想要退出 Lambda,就必须使用标签。禁止在 Lambda 中使用不带标签的 return,因为 Lambda 不能让外部的函数返回:

[code lang=”kotlin”]fun foo() {
ordinaryFunction {
return // ERROR: can not make foo return here
}
}[/code]

  但如果 Lambda 传递到的函数是内联的,则 return 也可以内联,所以允许使用 return

[code lang=”kotlin”]fun foo() {
inlineFunction {
return // OK: the lambda is inlined
}
}[/code]

  此类返回(位于 Lambda 中,但退出的是外部函数)称为非局部(Non-Local)返回。这种结构在包含内联函数的循环中很常见:

[code lang=”kotlin”]fun hasZeros(ints: List&ltInt&gt): Boolean {
ints.forEach {
if (it == 0) return true // returns from hasZeros
}
return false
}[/code]

  注意有些内联函数不会直接在函数体内调用通过参数传递给它的 Lambda,而是在另一个执行环境中调用,如局部 object 或嵌套函数。在此种情况下,不允许在 Lambda 中使用非局部控制流(Non-Local Control Flow)。为了标识这种情况,Lambda 参数需要使用 crossinline 修饰符标记:

[code lang=”kotlin”]inline fun f(crossinline body: () -&gt Unit) {
val f = object: Runnable {
override fun run() = body()
}
// …
}[/code]

  内联 Lambda 还不支持 breakcontinue,但我们有支持它们的计划。

Reified type parameters

  有时候需要访问以参数的形式传递的类型:

[code lang=”kotlin”]fun &ltT&gt TreeNode.findParentOfType(clazz: Class&ltT&gt): T? {
var p = parent
while (p != null && !clazz.isInstance(p)) {
p = p.parent
}
@Suppress(“UNCHECKED_CAST”)
return p as T?
}[/code]

  这里我们沿着树向上,使用反射检查节点是否有一个特定的类型。这没有任何问题,但在调用处却不太好看:

[code lang=”kotlin”]treeNode.findParentOfType(MyTreeNode::class.java)[/code]

  我们实际需要的是把一个类型传递给函数,像这样:

[code lang=”kotlin”]treeNode.findParentOfType&ltMyTreeNode&gt()[/code]

  为了达到上面的效果,内联函数支持具体化类型参数(Reified Type Parameter),可以这样写:

[code lang=”kotlin”]inline fun &ltreified T&gt TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}[/code]

这里使用 reified 修饰符限定了类型参数,然后它就可以在函数内被访问了,几乎就像是一个普通的类。由于函数是内联的,不需要使用反射,可以使用如 !isas 等普通的操作符。当然,也可以像前面提到的那样调用这个函数:myTree.findParentOfType<MyTreeNodeType>()

  虽然多数情况下并不需要用到反射,反射仍可以和具体化类型参数一同使用:

[code lang=”kotlin”]inline fun &ltreified T&gt membersOf() = T::class.members

fun main(s: Array) {
println(membersOf&ltStringBuilder&gt().joinToString(“\n”))
}[/code]

  普通函数(没有标记为内联)不能拥有具体化类型参数,没有运行时表示(Run-Time Representation)的类(如非具体化类型参数,或如 Nothing 的虚构类)不能用于具体化类型参数。

  更底层的描述,请参考规范文档

Inline properties (since 1.1)

  inline 修饰符可以用于没有支持字段(Backing Field)的属性的访问器,可以标注单个属性访问器:

[code lang=”kotlin”]val foo: Foo
inline get() = Foo()

var bar: Bar
get() = …
inline set(v) { … }[/code]

  也可以标注整个属性,这样会让两个访问器都成为内联:

[code lang=”kotlin”]inline var bar: Bar
get() = …
set(v) { … }[/code]

  在调用处,内联属性具有和普通内联函数一样的内联方式。