Kotlin Reference (十六) 函数和lambda表达式:内联函数、内联属性

KotLin 相关文档


官方在线Reference
kotlin-docs.pdf
Kotlin for android Developers 中文翻译
Kotlin开发工具集成,相关平台支持指南
Kotlin开源项目与Libraries
Kotlin开源项目、资源、书籍及课程搜索平台
Google’s sample projects written in Kotlin
Kotlin and Android

内联函数


内联函数:在调用该函数的地方,直接展开该函数。

非内联函数:在调用该函数的地方,将当前的环境参数压栈;再通过函数的内存地址,去访问它;当执行完成后,再出栈,回到原来的执行代码中,继续执行。

Java中没有明确的关键字,来手动实现内联函数。 是在运行时,通过JVM自动实现的。

内联的优点:省去切换访问地址,带来的时间开销
内联的缺点:会带来代码膨胀,增加了空间开销

在什么情况下适宜采用内联定义内联函数:被频繁使用,而且内容简单的情况下,宜定义为内联函数

Kotlin中可以使用inline关键字,来声明一个内联函数

如,一个加锁函数:

fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    } finally {
        lock.unlock()
    }
}
fun foo() {
    println("foo")
}

调用:

val l = ReentrantLock()
lock(l) {
  foo()
}

现,我们认定该加锁函数,会被频繁使用,所以将lock函数定义内联函数:

扫描二维码关注公众号,回复: 471921 查看本文章
inline fun <T> lock(lock: Lock, body: () -> T): T {
    ...
}

那么在调用时就会被Kotlin编译器处理成:

val l = ReentrantLock()
l.lock()
try {
    return println("foo")
} finally {
    lock.unlock()
}

定义非内联的函数形参

使用noinline关键字,可以定义非内联的函数形参

如下,内联函数foo,有两个函数型参数,后者使用了noinline修饰,所以它就是非内联的。即在内联函数foo被展开时,传入的inlined函数也会被展开,notInlined()函数,则不会展开

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

}

注意:如果一个使用了inline定义的函数,并不是一个高阶函数,或者其函数型参数中,没有inlinable的函数,编译器将发出警告,意味着这并不应该定义成一个内联函数。
还有种情形,即后文中的具体化关键字:reified 也是作用在一个内联函数中的,且可以不是一个高阶函数

局部返回与非局部返回

局部返回

在kotlin中使用return,一般只能用来退出一个有名字的函数或匿名函数。如果要在lambda函数中,使用return来退出lambda函数,那么对应的高阶函数可以声明成inline函数;如果不想将对应高阶函数定义成inline函数,那么在return语句后要加上@funName的标签。

像上面所述的是 “局部的return”

如,

fun ordinaryFunction(op: ()->Unit) {

调用:

fun foo2() {
    ordinaryFunction {
       return // ERROR: can not make `foo` return here
    }
    println("foo2")
}

这里return就会报错

改成:

return@ordinaryFunction

就可以退出ordinaryFunction函数。如果在这里使用return@foo2虽然也可以退出,但也仅是退出当前lambda函数,后续代码会继续执行的,不推荐这样使用。

如果将ordinaryFunction定义成内联函数,那么直接使用return就可以了

非局部的返回

return语句在lambda中,但是退出的却是整个外部函数。

如,

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

这里的 foreach函数中,return true语句退出的是函数hasZeros,若想要局部退出,需要使用标签:return@forEach

指定不能非局部返回

在内联函数中,可能没有直接调用lambda函数参数,而是通过一个局部对象中的函数,或其它函数来调用;这时在调用函数中可能包含非局部返回,这是不允许的,需要使用crossinline关键字,指明只能局部返回

如,

inline fun f(crossinline body: () -> Unit) {
    val func = Runnable {
        body()
    }
    //局部对象中
    object {
        fun funX() {
            body()
        }
    }
    // ...
}

调用:

fun testF() {
    f {
        listOf(1).forEach(return@f)
//        return@f

        println("ajajaj")
    }
}

因只能局部返回了,所以在return语句上,要加上@标签

具体化关键字:reified

具体化关键字:reified,用于泛型形参上,且函数要以inline修饰,而该函数不必须是一个高阶函数

先来看个例子,

interface TreeNode {
    var name: String
    var parent: TreeNode?
}

class MyTreeNode(override var name: String, override var parent: TreeNode?) : TreeNode { }

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && clazz.isInstance(p) && p.parent != null) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}

调用实现:

fun testFindParentOfType(): TreeNode? {
    val treeNodeA = MyTreeNode("A", null)
    val treeNodeB = MyTreeNode("B", treeNodeA)
    val treeNodeC = MyTreeNode("C", treeNodeB)

    return treeNodeC.findParentOfType(TreeNode::class.java)
}

如果将return treeNodeC.findParentOfType(TreeNode::class.java),改为return treeNodeC.findParentOfType(String::class.java),且将函数返回值,改成String?;编译没有问题,但运行时报错了,出错在return p as T?这句类型转换上:java.lang.ClassCastException: com.stone.fuctions.MyTreeNode cannot be cast to java.lang.String

所以,希望用泛型来在编译时,就固定传入的T类型,如,return treeNodeC.findParentOfType<TreeNode>)

那么,定义一个内联函数,并使用reified 修饰泛型形参:

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p.parent != null) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}

这时,在调用时:

// return treeNodeC.findParentOfType<TreeNode>()  //支持
// return treeNodeC.findParentOfType<MyTreeNode>()  //支持
return treeNodeC.findParentOfType()  //支持
//return treeNodeC.findParentOfType()<String>  //不支持

且省去了,之前入参Class对象的,类型判断语句;调用更简洁。

再例如,

inline fun <reified T> membersOf(): Collection<KCallable<*>> {
    return T::class.members
}

调用:

val str = membersOf<StringBuilder>().joinToString { kCallable ->  "$kCallable\n" }
println(str) //打印出StringBuilder的所有成员:属性和函数等

这个例子,比上一个更好。如果把reified去掉,return语句在编译器中直接报error了。说,”T::class” 语句中的T,必须是一个 reified(具体化)类型参数。

综上,inline fun <reified T> 函数,其中泛型形参,可以具体化为一个对应的kClass对象(即 “T::class” 语句返回的对象)。
kClass是Kotlin中提供的一个关于Class的反射操作的功能类,或者说工具类。一般可以通过 <Class>::class 来获取对象。(ps: 这样获取反射的kClass对象,并没有对所有java中的Class都支持,测试时,使用String就报错了,使用StringBuilder没问题)

内联属性


在kotlin1.1,inline可以用在属性的get/set访问器上

如,

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

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

若要对所有访问器都内联,也可以如下:

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

要注意的是:内联属性,相关的访问器中,不能使用field来指代属性自身。

var bar: Bar 
    get() = ...
    //inline set(v) { field = v } //直接报错
    inline set(v) { v } //ok

猜你喜欢

转载自blog.csdn.net/jjwwmlp456/article/details/76340802