Kotlin学习之-6.3 内联函数

来源:互联网 发布:mac为什么没有剪切 编辑:程序博客网 时间:2024/06/03 20:29

Kotlin学习之-6.3 内联函数

使用高阶函数会带来一些运行时的代价:每个函数都是一个对象,并且会捕获一个必报,例如,那些在函数体中访问的变量。会带来内存分配(对函数对象和类对象)和虚拟调用引入的额外开销

但是大多数情况下这种类型的开销可以使用内联lambda表达式来消除。下面展示的函数就是这种情况下很好的例子。例如,lock()函数可以很容易的做成内联的。考虑如下用例:

lock(l) { foo() }

编译器会省略如下代码,而不是去为参数和调用而创建一个函数对象。

l.lock()try {    foo()}finally {    l.unlock()}

这些难道不是我们一开始就想要的吗?

为了让编译器做这些工作,我们需要给lock()方法使用inline修饰符来标记。

inline fun lock<T>(lock: Lock, body: () -> T): T {}

inline修饰符会同时影响函数本身和传递给它的lambda表达式,所有这些都会内联至调用现场。

内联可能导致生成的代码变多,但是如果我们用合理的方式(不去内联太长的函数)来使用它,内联会带来性能上的提升,尤其是在循环中的”megamorphic”调用。

非内联

当你只是想要部分传递给内联函数的lambda表达式是内联的时候,你可以用noinline修饰符来标记这些函数参数。

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}

可以内联的lambda表达式只可以在内联函数中调用,或者传递给可以内联的参数,但是非内联的表达式noinline想怎么操作都可以:存储在成员中,或者函数之间传递等等。

注意如果一个内联函数没有可以内敛的函数参数并且没有实体类型参数,那么编译器会发出一个警告,因为内联这样的函数很有可能没有什么收益。

非局部返回

在Kotlin中,我们仅支持一种普通的,没有描述符的返回return从一个命名的函数或者匿名的函数中退出。这意味着如果想要从一个lambda表达式中退出,我们必须使用标签,并且单纯的return在lambda中是禁止的,因为lambda表达式无法控制所在的函数退出。

fun foo() {    ordinaryFuntion {        // 无法从foo中返回        return    }}

但是如果传递给函数的lambda表达式是内联的,那么返回也是内联的,这就可以支持:

fun foo() {    inlineFunction {        // 可以,因为是内联函数        return     }}

这种返回(在lambda中,却从外部的函数中返回)叫作非局部返回。我们习惯在内联函数的循环中使用它。

fun hasZeros(ints: List<Int>): Boolean {    ints.forEach {        // 从hasZeros 中返回        if (it == 0) return true    }    return false}

注意有些内联函数在调用当做参数传给他们的lambda表达式时,不是直接在函数主体内,而是在另外一个执行上下文中,例如一个局部对象或者一个内嵌函数。在这种情况下,非局部控制流同样不允许出现在lambda表达式中。为了表明这些,lambda参数需要使用一个crossinline修饰符。

inline fun f(crossinline body: () -> Unit) {    val f = object: Runnable {        override fun run() = body()    }}

目前在内联lambda表达式中还不支持break 和continue,但是正在考虑后续支持

实体类型参数

有时候我们需要访问一个当做参数传递的类型:

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {    var p = parent    while (p != null && !clazz.isInstance(p)) {        p = p.parent    }    @Suporee("UNCHECK_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修饰符来标识类型参数,现在它就可以在函数中范文,几乎就好像是一个正常的类一样。既然函数内联的,反射也就不需要了,正常的操作如!isas 就可以工作了。并且我们像上述那样调用它:myTree.findParentOfType<MyTreeNodeType>()

尽管反射在多数情况下可能不需要,但是我们仍然可以用实体类型参数来使用反射。

inline fun <reified T> membersOf() = T::class.membersfun main(s: Array<String>) {    println(membersOf<StringBuilder>().joinToString("\n")}

普通函数(非内联函数)不可以使用实体类型参数。一个没有运行时的表达方式的类型不能被用作实体参数作为参数。

具体细节,可以参考规格文档

内联属性(从Kotlin v1.1支持)

inline修饰符可以用在没有backing field的属性的访问器上。 你可以给单独给属性的访问器注解。

val foo: Foo    inline get() = Foo()var bar: Bar    get() = { }    inline set(v) { }

你也可以注解整个属性,这样属性的两个访问器都是内联的。

inline var bar: Bar    get() = { }    set(v) = { }

在调用时,内联的访问器和普通的内联函数式一样的内联方式。


PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发

原创粉丝点击