使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,且这个函数对象捕获了一个闭包。也就是说,闭包内的变量可以在函数对象内部访问。内存分配(为函数对象和类)和实际调用将引入运行时间开销。
但通过使用内联λ表达式,可以避免这些情况的出现。下面的函数是这种情况的很好例子。lock()
函数可以非常容易的在调用处被内联。考虑下面的情况:
lock(l) { foo() }
编译器没有为参数创建一个函数对象并生成一个调用。而是生成了如下代码:
l.lock()
try {
foo()
}
finally {
l.unlock()
}
这不就是我们一开始想要的?
为了让编译器这么做,我们需要使用inline
修饰符来标记lock()
函数:
inline fun lock<T>(lock: Lock, body: () -> T): T {
// ...
}
这个inline修饰符将影响函数本身和传给给函数的λ表达式:所有这些都将内联到调用处。
内联可能导致生成代码的增加,但如果我们使用得当(不内联大函数),它将在性能上有所提升,尤其是在循环内部的超多态调用处。
非内联(noinline)
如果你只想传入内联函数中的一部分λ表达式被内联,你可以使用noinline
修饰符修饰那些不想被内联的形参:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ...
}
内联λ表达式只能在内联函数内部调用,或者作为内联参数传递,但是那些被noinline修饰符标记的参数则可以按我们任何想用的方式操作:存储在字段中,或传递。
注意:如果一个内联函数没有可内联的函数参数,且没有具体化类型的参数,编译器将产生警告,因为内联这样的函数很可能没有益处(若你认为内联是必须的,则可以关闭该警告)。
非局部返回(Non-local returns)
在Kotlin中,我们可以使用一个常规的,没有任何修饰符的return
语句来返回一个有名函数或匿名函数。这以为要想只返回一个λ表达式,我们必须使用标签,在λ表达式禁止使用单独一个return
语句,因为λ表达式不能使包含它的封闭函数返回:
fun foo() {
ordinaryFunction {
return // ERROR: can not make `foo` return here
}
}
但是如果传入函数的λ表达式是内联的,则该return也可以内联,如下是允许的:
fun foo() {
inlineFunction {
return // OK: the lambda is inlined
}
}
这种返回(位于λ表达式中,但退出的是包含该λ表达式的封闭函数)称为非局部返回。我们习惯了在循环中使用这种结构,循环内部通常置入内联函数:
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // returns from hasZeros
}
return false
}
注意:一些内联函数有可能调用的作为其参数传入的λ表达式不直接在函数体中,而是执行另一个上下文的λ表达式,例如一个局部对象或一个嵌套函数。这是,非局部返回在这种λ表达式将不能使用。为了说明这种情况,λ表达式参数需要以crossinline
修饰符标记:
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}
break和continue在内联λ表达式中不允许使用,但我们正在计划支持它们。
具体类型参数(Reified type parameters)
有时候我们需要访问一个类型,这个类型作为参数传递给我们:
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?
}
我们向上遍历一棵树并检查每一个节点是否是指定类型。但方法的调用则不是很美观直接:
treeNode.findParentOfType(MyTreeNode::class.java)
事实上我们仅想传递一个类型给这个函数,就像这样:
treeNode.findParentOfType<MyTreeNode>()
为了能够这么做,内联函数支持具体化的类型参数,因此我们可以这样写:
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>()
虽然在需要情况下不需要反射,我们仍然可以对一个具体化的类型参数使用它:
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
普通函数(未被inline标记)不能有具体化参数。不具有运行时表示的类型(例如非具体化的类型参数或类似于Nothing的虚构类型)不能用作具体化的类型参数的实参。
内联属性(Inline properties (since 1.1))
修饰符inline可以用于没有后背字段的属性的访问器。可以单独注解属性访问器:
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
也可以注解一个睡醒,将两个访问器都标记为内联:
inline var bar: Bar
get() = ...
set(v) { ... }
在调用处,内联访问将被作为常规内联函数一样被内联。