Kotlin学习笔记4-3 函数和lambda-内联函数

内联函数

Kotlin官网:Functions and Lambdas-Inline Functions
高阶函数中,参数中的函数作为一个对象,需要创建对应的对象,分配内存和闭包,这些都是额外的开销。对于一些场景,只是执行函数参数中的内容,无须创建单独对象,例如lock()函数:

lock(l) { foo() }

编译器可以转换成:

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

这样无需创建函数对象,节省相关的开销。要实现这个效果,使用inline修饰函数:

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

inline修饰符同时作用于被修饰的函数自身和参数中的函数,高阶函数自身和参数中的函数都会与调用者内联。
内联函数会使生成的代码增多,要合理使用,例如避免内联特别复杂的函数,合理使用可以有效提高程序效率。

不内联

使用noinline关键字修饰函数参数,可以指定某个参数不内联:

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

由于内联函数只能在内联函数中调用或是作为内联参数传递,当需要将函数作为一个对象,给其他成员赋值,或是传递参数,将对应参数标记为非内联即可。
如果函数标记为内联函数,但是没有任何内联函数参数或是具体化类型参数,编译器会报警,因为这样是不会提高效率的。要消除警告使用@Suppress("NOTHING_TO_INLINE")注解。

非局部返回值

Kotlin中return只能退出fun声明的函数,如普通函数和匿名函数,如果要退出lambda表达式要使用标签,lambda中禁止纯return的写法:

fun foo() {
    ordinaryFunction {
        return // 此处出错,不允许在lambda中return foo函数
    } 
}

如果lambda是所谓内联函数,则return也会内联,此时可以用return返回

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

这种写在lambda表达式内,但是返回外围函数的return称为”non-local”非局部的返回。例如在循环中返回值:

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

在不直接执行参数中的函数时,例如在对象或是嵌套函数中执行,此时非局部返回无效,为了让调用者知道,需要用crossinline修饰参数,此时调用者使用非局部返回时会报错:

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

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)

而实际上Kotlin支持用泛型传递类型:

treeNode.findParentOfType<MyTreeNode>()

这里是用reified保留了泛型信息实现的:

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

使用reified修饰函数的泛型类型,之后就可以像普通类型那样使用泛型T了。
具体化的泛型信息也可以用来反射:

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
    println(membersOf<StringBuilder>().joinToString("\n"))
}

关于具体化类型的底层实现说明详见Kotlin特殊文档
普通函数的泛型信息在编译时会被擦除,无法在运行时获取相关信息,只有内联函数的具体化参数才会保留泛型的类型信息。

内联属性(Kotlin1.1后支持)

对于没有使用后备字段的属性可以用inline修饰访问器,使用inline修饰单独的get/set,或者修饰属性使get/set都内联:

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

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

对于访问者来说,内联属性的内联规则和内联函数相同。

公共API内联函数的限制

对于访问限制为public或是protected的函数,认为是module对外提供的API,此时用内联函数,考虑一个问题:修改了公开API的内联函数的module,并重新编译,而内联函数调用的其他module没有,此时会出现兼容性问题。
所以Kotlin规定公共API内联函数不允许调用非公开的内容(private或是Internal的)。
如果一定要在公共API中调用非公开内容,给被调用的内容增加@PublishedApi注解,编译时会被当做公开内容处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值