Kotlin笔记9-Inline functions 内联函数 和 noinline crossinline修饰符

目录

一、 Inline functions

二、noinline

三、Non-local returns

四、Reified type parameters  具体参数类型

五、内联属性 Inline properties (since 1.1)


一、 Inline functions

参考官网对于Inline functions的介绍:http://kotlinlang.org/docs/reference/inline-functions.html

在官网对于函数和lambda表达式介绍的一节中,有以下一段话:

Inline functions

Sometimes it is beneficial to use inline functions, which provide flexible control flow, for higher-order functions.

有时候在高阶函数编程中,使用内联函数Inline functions,可以灵活地控制程序的流转。

按照官网的说明:

Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure, i.e. those variables that are accessed in the body of the function. Memory allocations (both for function objects and classes) and virtual calls introduce runtime overhead.

But it appears that in many cases this kind of overhead can be eliminated by inlining the lambda expressions. 

使用谷歌翻译后如下:

使用高阶函数会产生某些运行时惩罚:每个函数都是一个对象,它捕获一个闭包,即在函数体中访问的那些变量。 内存分配(包括函数对象和类)和虚拟调用都会引入运行时开销。

Kotlin的Lambda是一个匿名对象。可以使用inline修饰方法,这样当方法在编译时就会拆解方法的调用为语句调用(可以理解成将inline函数变成相应的拆解后的语句顺序执行),进而减少创建不必要的对象。

但似乎在许多情况下,通过内联lambda表达式可以消除这种开销。

官网给出了一个例子,lock()函数可以很容易的内联到调用方,

lock(l) { foo() }

这时编译器能够生成以下代码:

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

通过这种内联到调用方“l”的方式,可以不用为参数创建一个函数变量并生成一个对该函数变量的调用,以此降低运行时开销。

为了能让编译器做这些操作,需要lock()前使用inline修饰符。

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

内联修饰符既影响函数本身又影响传递给它的lambda:所有这些都将内联到调用站点中。

内联可能会导致生成的代码增长; 但是,如果我们以合理的方式进行(即避免内联大函数),它将在性能上得到回报,特别是在循环内部的“变形”调用站点( especially at "megamorphic" call-sites inside loops.)。

由于使用过度使用内联函数会增加编译器的编译负担,所以一般只在高阶函数中才使用内联函数。

二、noinline

如果您只想要传递给内联函数的lambda表达式是内联的,可以使用noinline修饰符标记该lambda表达式的函数参数:

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

Inlinable lambdas只能在内联函数内部调用或作为inlinable参数传递,但是noinline的可以以我们喜欢的任何方式进行操作:存储在字段中,传递等等。

请注意,如果内联函数没有inlinable函数参数且没有reified类型参数,编译器将发出警告,因为内联此类函数不太可能有用(如果您确定需要使用内联,则可以使用@Suppress("NOTHING_TO_INLINE")注解来取消警告

三、Non-local returns

在Kotlin中,我们只能使用正常的,不加修饰符的return来退出命名函数或匿名函数。 这意味着要退出lambda,我们必须使用一个标签 label并且在lambda中禁止只使用一个单独的return语句,因为lambda不能使其外围函数返回

fun ordinaryFunction(block: () -> Unit) {
    println("hi!")
}
fun foo() {
    ordinaryFunction {
        return // ERROR: can not make `foo` return here
    }
}
fun main(args: Array<String>) {
    foo()
}

但是如果lambda表达式传递到的这个函数被inline修饰了,那么这时候,return语句也被内联了,及可以从外围函数返回

fun foo() {
    inlined {
        return // OK: the lambda is inlined
    }
}

像这种存在于在lambda表达式中,返回其外围函数的return语句叫做非本地返回non-local returns。这种return语句经常用在循环体当中,且外围函数就是被内联函数包裹

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // returns from hasZeros
    }
    return false
}
//forEach是内联函数
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

⚠️注意:有些情况下,传给内联函数的lambda表达式,不直接在函数体中调用,而是却是为了另一个可执行的上下文,比如在一个局部变量或者一个嵌套函数中调用。在这种情况下,lambda表达式不允许中断外部函数执行(non-local control flow is also not allowed in the lambdas.),为了表明这一点,lambda参数需要用crossinline修饰符标记:

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

Kotlin正计划在以后的版本中,针对内联的lambda表达式,添加其对break和continue的支持。

 

总结:

1. 在Kotlin中,内部lambda是不允许中断外部函数执行的

2. inline的lambda可以中断外部函数调用

3. crossinline 不允许inline的lambda中断外部函数执行

4. noinline 拒绝内联,即不会将该lambda表达式拆解出来

val runnable = Runnable {
    println("runnalbe")
}

 inline fun test1(l: () -> Unit) {
    l.invoke()
    return
}

inline fun test2(l0: () -> Unit, noinline l1: () -> Unit): () -> Unit {
    l0.invoke()
    l1.invoke()
    println("test2")
    return l1
}

fun main(args: Array<String>) {
    test1 {
        println("hello")
        return  //要在这里使用return 必须将test1函数用inline修饰(参数l不能用crossinline修饰),这时程序流将直接返回到main函数外,即不再执行以下语句/
        println("hello1")
    }
    println("hello2")
    test2({ println("cyc") }, runnable::run)
}

//这时程序输出:
hello

//如果想不输出hello1,但是要继续输出hello2,则要使用crossinline修饰test1函数的参数l,同时return要改成return@test1,即:

inline fun test1(crossinline l: () -> Unit) {
    l.invoke()
    return
}


//这里函数返回值类型为lambda,如果参数l1不使用noinline修饰,那么在编译时,会将l1拆解到main函数中,
//那么这时候函数返回值类型为lambda就变成了main函数的返回值类型,这显然是不正确的
//所以这时候不能将l1进行拆解,即使用noinline修饰参数l1
inline fun test2(l0: () -> Unit, noinline l1: () -> Unit): () -> Unit {
    l0.invoke()
    l1.invoke()
    println("test2")
    return l1
}

fun main(args: Array<String>) {
    test1 {
        println("hello")
        return@test1
        println("hello1")
    }
    println("hello2")
    test2({ println("cyc") }, runnable::run)
}

四、Reified type parameters  具体参数类型

参考:https://github.com/JetBrains/kotlin/blob/master/spec-docs/reified-type-parameters.md

Goal: support run-time access to types passed to functions, as if they were reified (currently limited to inline functions only

有时候,我们需要使用作为参数传递来的一个类型type,如:

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?
}

这里,我们遍历这个tree,并使用反射类检查一个节点是否为制定type类型。看起来没有问题,但是在调用该方法的时候就显得不是很简洁:

treeNode.findParentOfType(MyTreeNode::class.java)

而我们实际上只是想传递一个类型type到这个函数当中,即像这样调用:

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 就可以起到作用。

尽管在很多情况下不再需要使用反射,我们仍然能结合具体化的类型参数 reified type parameter:来使用发射:

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)

内联修饰符可用于没有支持字段backing field的属性的访问器。 您可以注释单个属性访问者,或者修饰整个属性,这时属性的setter和getter方法将都被标记为内联的:

val foo: Foo
    inline get() = Foo()

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

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

这时候,在调用方,被inline修饰的setter和getter如同普通的内联函数一样被内联到调用方。

猜你喜欢

转载自blog.csdn.net/unicorn97/article/details/81535792