Kotlin-内联函数

来源:互联网 发布:算法导论第三版pdf下载 编辑:程序博客网 时间:2024/06/10 09:47

使用高阶函数肯定有不小的代价:每个函数都是一个对象并且它还是闭包,例如,函数内的局部变量只能在函数体内访问,内存分配(函数对象和类)和虚拟调用都会导致运行时开销增大。

但它在很多情况下的出现的开销还是可以通过内联lambda表达式消除,在下面的这个函数就是一个很好的实例,例如lock()函数可以很轻易的内联到调用处,考虑以下情况:

lock(l) { foo() }

编译器可能会生成以下代码,而不是为参数创建函数对象并生成调用

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

按这样来看,这不是我们从一开始就想要的吗?
要想编译这样做,我们需要用inline来修饰lock()函数

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

inline修饰符会影响函数本身和lambda表达式传给它的值,所有这些将内联到调用处,

内联会导致代码量的增加,但如果我们使用得当(不要在大函数中使用),那么它将只会消耗部分性能,特别是在循环内部使用(especially at “megamorphic” call-sites inside loops.)

非内联(noinline)

如果你只想lambdas的部分传递是内联的,那么你可以在函数参数中使用noinline修饰符来避免内联

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

内联lambda只能叫里面的内联函数或内联参数传递,但被noinline修饰的话则可以任意使用,存储在字段中、经过字段传递等.

==需要注意的是,如果内联函数没有可内联的函数参数,并且没有具体的参数类型,那么编译器就会发出警告,因为内联等功能很可能是有益的(你可以抑制警告如果你确信内联是必要的).==

非本地返回

在Kotlin中,我们只可以使用普通且不是任意的return来退出命名函数或匿名函数,这就意味着我们要退出lambda必须使用标签,在lambda内部是禁止直接使用return,因为在lambda中使用return保证不了函数闭合:

fun foo() {    ordinaryFunction {        return // ==错误用法,这里不能使foo返回==    }}

但如果函数是通过lambda来达到内联,那么return就和内联一样有效,比如它可以这样:

fun foo() {    inlineFunction {        return // OK,lambda内联    }}

像这样的return(在lambdas中就可以退出闭合函数)被称为非本地返回,我们已经习惯了这种包含内联函数的循环结构:

fun hasZeros(ints: List<Int>): Boolean {    ints.forEach {        if (it == 0) return true // returns from hasZeros    }    return false}

==注意:在一些局部变量或嵌套函数中,一些内联函数作为Lambda表达式参数的时候,不可以直接在函数体内使用,但可以在另外一个上下文中使用。在这种情况下,在Lambdas中也是不允许非本地控制流。为解决这种情况,Lambdas的参数需要使用crossinline关键字来修饰:==

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

在内联Lambdas中,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)

我们实际上需要的是将一个类型传递给这个函数,它可以这样使用:

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.membersfun main(s: Array<String>) {    println(membersOf<StringBuilder>().joinToString("\n"))}

普通函数(就是没有被inline标识的函数)是不可以有具体类型参数的,没有在运行时被明确表示的类型(非具体类型参数或虚构类型Nothing)是不可以被用具体类型参数.

内联属性(版本1.1以后)

当属性的访问器没有隐性支持属性时可以被inline修饰,可以标识自定义属性的访问器:

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

还可以对整个属性进行标识,这表示该属性的所有访问器都被inline修饰:

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

被调用的时候,内联访问器具备和内联函数一样的规则。

原创粉丝点击