Kotlin进阶之内联函数(Inline Functions)

使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,且这个函数对象捕获了一个闭包。也就是说,闭包内的变量可以在函数对象内部访问。内存分配(为函数对象和类)和实际调用将引入运行时间开销。

但通过使用内联λ表达式,可以避免这些情况的出现。下面的函数是这种情况的很好例子。lock()函数可以非常容易的在调用处被内联。考虑下面的情况:

lock(l) { foo() }

编译器没有为参数创建一个函数对象并生成一个调用。而是生成了如下代码:

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

这不就是我们一开始想要的?

为了让编译器这么做,我们需要使用inline修饰符来标记lock()函数:

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

这个inline修饰符将影响函数本身和传给给函数的λ表达式:所有这些都将内联到调用处。

内联可能导致生成代码的增加,但如果我们使用得当(不内联大函数),它将在性能上有所提升,尤其是在循环内部的超多态调用处。

非内联(noinline)

如果你只想传入内联函数中的一部分λ表达式被内联,你可以使用noinline修饰符修饰那些不想被内联的形参:

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

内联λ表达式只能在内联函数内部调用,或者作为内联参数传递,但是那些被noinline修饰符标记的参数则可以按我们任何想用的方式操作:存储在字段中,或传递。

注意:如果一个内联函数没有可内联的函数参数,且没有具体化类型的参数,编译器将产生警告,因为内联这样的函数很可能没有益处(若你认为内联是必须的,则可以关闭该警告)。

非局部返回(Non-local returns)

在Kotlin中,我们可以使用一个常规的,没有任何修饰符的return语句来返回一个有名函数或匿名函数。这以为要想只返回一个λ表达式,我们必须使用标签,在λ表达式禁止使用单独一个return语句,因为λ表达式不能使包含它的封闭函数返回:

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

但是如果传入函数的λ表达式是内联的,则该return也可以内联,如下是允许的:

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

这种返回(位于λ表达式中,但退出的是包含该λ表达式的封闭函数)称为非局部返回。我们习惯了在循环中使用这种结构,循环内部通常置入内联函数:

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在内联λ表达式中不允许使用,但我们正在计划支持它们。

具体类型参数(Reified type parameters)

有时候我们需要访问一个类型,这个类型作为参数传递给我们:

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修饰符来修饰类型参数,这时候可以在函数内部访问类型参数,就像是一个普通的类。由于函数是内联的,不需要反射,常规操作符如!isas都可以正常使用。现在我们可以如下调用上个函数:

myTree.findParentOfType<MyTreeNodeType>()

虽然在需要情况下不需要反射,我们仍然可以对一个具体化的类型参数使用它:

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))

修饰符inline可以用于没有后背字段的属性的访问器。可以单独注解属性访问器:

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

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

也可以注解一个睡醒,将两个访问器都标记为内联:

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

在调用处,内联访问将被作为常规内联函数一样被内联。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容