内联函数
Kotlin官网:Functions and Lambdas-Inline Functions
高阶函数中,参数中的函数作为一个对象,需要创建对应的对象,分配内存和闭包,这些都是额外的开销。对于一些场景,只是执行函数参数中的内容,无须创建单独对象,例如lock()函数:
lock(l) { foo() }
编译器可以转换成:
l.lock()
try {
foo()
}
finally {
l.unlock()
}
这样无需创建函数对象,节省相关的开销。要实现这个效果,使用inline
修饰函数:
inline fun <T> lock(lock: Lock, body: () -> T): T {
// ...
}
inline修饰符同时作用于被修饰的函数自身和参数中的函数,高阶函数自身和参数中的函数都会与调用者内联。
内联函数会使生成的代码增多,要合理使用,例如避免内联特别复杂的函数,合理使用可以有效提高程序效率。
不内联
使用noinline
关键字修饰函数参数,可以指定某个参数不内联:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ...
}
由于内联函数只能在内联函数中调用或是作为内联参数传递,当需要将函数作为一个对象,给其他成员赋值,或是传递参数,将对应参数标记为非内联即可。
如果函数标记为内联函数,但是没有任何内联函数参数或是具体化类型参数,编译器会报警,因为这样是不会提高效率的。要消除警告使用@Suppress("NOTHING_TO_INLINE")
注解。
非局部返回值
Kotlin中return只能退出fun声明的函数,如普通函数和匿名函数,如果要退出lambda表达式要使用标签,lambda中禁止纯return的写法:
fun foo() {
ordinaryFunction {
return // 此处出错,不允许在lambda中return foo函数
}
}
如果lambda是所谓内联函数,则return也会内联,此时可以用return返回
fun foo() {
inlineFunction {
return // OK: the lambda is inlined
}
}
这种写在lambda表达式内,但是返回外围函数的return称为”non-local”非局部的返回。例如在循环中返回值:
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暂不支持。
具体化参数类型
在需要类型信息时,一般做法是在函数参数中传递类型信息:
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)
而实际上Kotlin支持用泛型传递类型:
treeNode.findParentOfType<MyTreeNode>()
这里是用reified
保留了泛型信息实现的:
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}
使用reified修饰函数的泛型类型,之后就可以像普通类型那样使用泛型T了。
具体化的泛型信息也可以用来反射:
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
关于具体化类型的底层实现说明详见Kotlin特殊文档
普通函数的泛型信息在编译时会被擦除,无法在运行时获取相关信息,只有内联函数的具体化参数才会保留泛型的类型信息。
内联属性(Kotlin1.1后支持)
对于没有使用后备字段的属性可以用inline修饰访问器,使用inline修饰单独的get/set,或者修饰属性使get/set都内联:
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
inline var bar: Bar
get() = ...
set(v) { ... }
对于访问者来说,内联属性的内联规则和内联函数相同。
公共API内联函数的限制
对于访问限制为public或是protected的函数,认为是module对外提供的API,此时用内联函数,考虑一个问题:修改了公开API的内联函数的module,并重新编译,而内联函数调用的其他module没有,此时会出现兼容性问题。
所以Kotlin规定公共API内联函数不允许调用非公开的内容(private或是Internal的)。
如果一定要在公共API中调用非公开内容,给被调用的内容增加@PublishedApi
注解,编译时会被当做公开内容处理。