Kotlin学习(十八)—— 内联函数

为什么要有内联函数

使用高阶函数会带来⼀些运⾏时的效率损失:每⼀个函数都是⼀个对象,并且会捕获⼀个闭包。即那些在函数体内会访问到的变量。内存分配(对于函数对象和类)和虚拟调用会引⼊运行时间开销。
但是在许多情况下通过内联化 lambda 表达式可以消除这类的开销。下述函数是这种情况的很好的例子。即 lock() 函数可以很容易地在调用处内联。
考虑下⾯的情况:

fun  lock(lock: Lock, body :() -> T):T {
    lock.lock()
    try {
        return body()
    } finally {
        lock.unlock()
    }
}
fun foo(){
    println("函数被调用了")
}
//接下来我们调用lock()函数
fun main(args: Array) {
    lock(ReentrantLock()) {
        foo()
    }
}

上面的代码传入了一个lambda表达式,编译期会把这个表达式看做一个对象处理。而不是像下面的这样把代码像如下的方式生成:

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

为了让编译器生成上面代码,我们就要使用inline标记lock()函数

inline fun  lock(lock: Lock, body :() -> T):T {
    lock.lock()
    try {
        return body()
    } finally {
        lock.unlock()
    }
}

上面的lock()函数被iniline修饰了,调用lock()时,lock函数中的代码,和传入它的表达式,都会代码原样的被编译器重新复制一遍到调用lock()的地方。
inline 修饰符影响函数本身和传给它的 lambda 表达式:所有这些都将内联到调用处。
内联可能导致生成的代码增加,但是如果我们使用得当(不内联大函数),它将在性能上有所提升,尤其是在循环中的“超多态(megamorphic)”调用处。
(这里小编并不知道好多态是什么东西)

禁用内联

如果你只想被(作为参数)传给⼀个内联函数的 lamda 表达式中只有⼀些被内联,你可以用 noinline 修饰符标记⼀些函数参数,如下:

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

可以内联的 lambda 表达式只能在内联函数内部调用或者作为可内联的参数传递,但是 noinline 的可以以任何我们喜欢的方式操作:存储在字段中、传送它等等。
需要注意的是,如果⼀个内联函数没有可内联的函数参数并且没有具体化的类型参数,编译器会产生⼀个警告,因为内联这样的函数很可能并无益处(如果你确认需要内联,则可以关掉该警告)。

非局部返回

在 Kotlin 中,我们可以只使用一个正常的、非限定的 return 来退出⼀个命名或匿名函数。这意味着要退出⼀个 lambda 表达式,我们必须使用⼀个标签,并且在 lambda 表达式内部禁止使用裸 return ,因为 lambda 表达式不能使包含它的函数返回。
这是因为lambda 表达式中的 return 将从包含它的函数返回,⽽匿名函数中的 return 将从匿名函数⾃⾝返回。如果lambda使用裸返回,包含他的函数也会返回(一般情况下,编译期禁止lambda裸返回)

fun say(sayHello : () -> Unit){
    println(sayHello())
}
fun main(args: Array) {
    say {
        //return //这里会报错
        return@say //这里使用标签就不会错
    }
}

但是如果 lambda 表达式传给的函数是内联的,该 return 也可以内联,所以它(裸return)是允许的
实际上是因为

inline fun say(sayHello : () -> String){
    println(sayHello())
    println("lambda表达式返回") //这句话能执行
}

fun main(args: Array) {
    say {
        return@say "hello" //这里使用标签就不会错,因为这里是从lambda表达式返回
        return //这里也不会报错,这里是从say()返回,但是这不会被执行,因为lambda表达式已经返回了
    }
}

但是需要注意的是,上面的lambda表达式还是不能使用return “hello”老代替return@say “hello”,因为lambda表达式使用return还是一样的从包裹它的函数返回

这种返回(位于 lambda 表达式中,但退出包含它的函数)称为非局部返回。我们习惯了在循环中用这种结构,其内联函数通常包含:

fun hasZero(array: Array):Boolean{
    for (i in array){
        if (i == 0) return true
    }
    return false
}

请注意,⼀些内联函数可能调用传给它们的不是直接来自函数体、而是来自另⼀个执⾏上下⽂的 lambda 表达式参数,例如来自局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许非局部控制流。为了标识这种情况,该 lambda 表达式参数需要用 crossinline 修饰符标记。

//虽然这个函数式内联的,但是函数参数被crossinline标记,所以传入的表达式不允许非局部返回
inline fun f(crossinline body: () -> Unit) {  
    val f = object: Runnable {
        override fun run() = body()
    }
    f.run()
}
fun main(args: Array) {
    f { 
        return  //这里编译期会报错,因为不允许非局部返回
    }
}

下面这种情况也是一样的,不能非局部返回:

inline fun compare(crossinline body:(value:Int,other :Int) -> Boolean){
    var  a= 9
    val c = object : Comparable<Int>{
         val value = a
         override fun compareTo(other: Int): Int {
             if (body(value,other)){
                 return 1
             }else{
                 return -1
             }
        }
    }
    println(c.compareTo(3))
}
fun main(args: Array) {
    compare(){
        x,y ->if (x>y)true else false
        //return在这里同样不允许使用
    }
}

break 和 continue 在内联的 lambda 表达式中在Kotlin中还不可⽤,但是也计划⽀持它们(小编希望kotlin能在语言榜排进前十,kotlin有日趋完善,因为kotlin真心是一个好的语言)

具体化的类型参数

有时候我们需要访问⼀个作为参数传给我们的⼀个类型:

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  TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

我们使⽤ reified 修饰符来限定类型参数,现在可以在函数内部访问它了,几乎就像是⼀个普通的类⼀样。由于函数是内联的,不需要反射,正常的操作符如 !is 和 as 现在都能⽤了。此外,我们还可以按照上面提到的方式调用它:myTree.findParentOfType()

inline fun  membersOf() = T::class.members
fun main(s: Array) {
    println(membersOf().joinToString("\n"))
}

普通的函数(未标记为内联函数的)不能有具体化参数。不具有运行时表示的类型(例如非具体化的类型参数或者类似于 Nothing 的虚构类型)不能用作具体化的类型参数的实参

内联属性

inline 修饰符可⽤于没有幕后字段的属性的访问器。你可以标注独⽴的属性访问器:

class A {
    val s : String
        inline get() = String("hello".toByteArray())
    inline var s1 : String
        get() = String("lala".toCharArray())
        set(value) {
            println("$value")
        }

}

fun main(args: Array<String>) {
    var a = A()
    a.s1="lal"
    println(a.s1)
}

公有 API 内联函数的限制

当⼀个内联函数是 public 或 protected 而不是 private 或 internal 声明的⼀部分时,就会认为它是⼀个模块级的公有 API。可以在其他模块中调用它,并且也可以在调用处内联这样的调用。

这带来了⼀些由模块做这样变更时导致的⼆进制兼容的⻛险⸺声明⼀个内联函数但调用它的模块在它修改后并没有重新编译。(A 调用了内联函数B,然后B修改了,A的调用处是不会重新编译的)

为了消除这种由非公有 API 变更引⼊的不兼容的风险,公有 API 内联函数体内不允许使用非公有声明,即,不允许使用private 与 internal 声明以及其部件。

private fun hello(){
    println("hello")
}

inline fun foo(){
    //下面的函数调用会报错
    hello()  //foo()是共有的内联API,所以为了避免二进制风险,函数体里面不能调用非公有的API(hello()函数式私有的)
}

上面的代码编译期会报错:Public-API inline function cannot access non-public-API ‘private fun hello(): Unit defined in B_functionAndLambda in file C_inlineFuntion.kt’ (注意加粗的黑体字的报错)

但是如果hello()函数共有API,那么就可以调用了

fun hello(){
    println("hello")
}

inline fun ff(){
    hello()  //内联函数只要调用共有的APi,就能编译通过
}

⼀个 internal 声明可以由 @PublishedApi 标注,这会允许它在公有 API 内联函数中使用。当⼀个 internal 内联函数标记有 @PublishedApi时,也会像公有函数⼀样检查其函数体。

internal fun hello(){
    println("hello")
}

inline fun ff(){
    hello()  //这样调用也会编译报错,即使是internal模块级的,也会编译不通过。
}

但是下面的代码会通过

@PublishedApi
internal fun hello(){
    println("hello")
}

inline fun ff(){
    //这里的代码不会报错,hello()函数使用了@PublishedApi标记,hello()函数也会像公有函数⼀样检查其函数体
    hello() 
}

你可能感兴趣的:(kotlin语言)