Kotlin進(jìn)階之內(nèi)聯(lián)函數(shù)(Inline Functions)

使用高階函數(shù)會帶來一些運(yùn)行時的效率損失:每一個函數(shù)都是一個對象,且這個函數(shù)對象捕獲了一個閉包。也就是說,閉包內(nèi)的變量可以在函數(shù)對象內(nèi)部訪問。內(nèi)存分配(為函數(shù)對象和類)和實(shí)際調(diào)用將引入運(yùn)行時間開銷。

但通過使用內(nèi)聯(lián)λ表達(dá)式,可以避免這些情況的出現(xiàn)。下面的函數(shù)是這種情況的很好例子。lock()函數(shù)可以非常容易的在調(diào)用處被內(nèi)聯(lián)。考慮下面的情況:

lock(l) { foo() }

編譯器沒有為參數(shù)創(chuàng)建一個函數(shù)對象并生成一個調(diào)用。而是生成了如下代碼:

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

這不就是我們一開始想要的?

為了讓編譯器這么做,我們需要使用inline修飾符來標(biāo)記lock()函數(shù):

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

這個inline修飾符將影響函數(shù)本身和傳給給函數(shù)的λ表達(dá)式:所有這些都將內(nèi)聯(lián)到調(diào)用處。

內(nèi)聯(lián)可能導(dǎo)致生成代碼的增加,但如果我們使用得當(dāng)(不內(nèi)聯(lián)大函數(shù)),它將在性能上有所提升,尤其是在循環(huán)內(nèi)部的超多態(tài)調(diào)用處。

非內(nèi)聯(lián)(noinline)

如果你只想傳入內(nèi)聯(lián)函數(shù)中的一部分λ表達(dá)式被內(nèi)聯(lián),你可以使用noinline修飾符修飾那些不想被內(nèi)聯(lián)的形參:

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

內(nèi)聯(lián)λ表達(dá)式只能在內(nèi)聯(lián)函數(shù)內(nèi)部調(diào)用,或者作為內(nèi)聯(lián)參數(shù)傳遞,但是那些被noinline修飾符標(biāo)記的參數(shù)則可以按我們?nèi)魏蜗胗玫姆绞讲僮鳎捍鎯υ谧侄沃校騻鬟f。

注意:如果一個內(nèi)聯(lián)函數(shù)沒有可內(nèi)聯(lián)的函數(shù)參數(shù),且沒有具體化類型的參數(shù),編譯器將產(chǎn)生警告,因?yàn)閮?nèi)聯(lián)這樣的函數(shù)很可能沒有益處(若你認(rèn)為內(nèi)聯(lián)是必須的,則可以關(guān)閉該警告)。

非局部返回(Non-local returns)

在Kotlin中,我們可以使用一個常規(guī)的,沒有任何修飾符的return語句來返回一個有名函數(shù)或匿名函數(shù)。這以為要想只返回一個λ表達(dá)式,我們必須使用標(biāo)簽,在λ表達(dá)式禁止使用單獨(dú)一個return語句,因?yàn)棣吮磉_(dá)式不能使包含它的封閉函數(shù)返回:

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

但是如果傳入函數(shù)的λ表達(dá)式是內(nèi)聯(lián)的,則該return也可以內(nèi)聯(lián),如下是允許的:

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

這種返回(位于λ表達(dá)式中,但退出的是包含該λ表達(dá)式的封閉函數(shù))稱為非局部返回。我們習(xí)慣了在循環(huán)中使用這種結(jié)構(gòu),循環(huán)內(nèi)部通常置入內(nèi)聯(lián)函數(shù):

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

注意:一些內(nèi)聯(lián)函數(shù)有可能調(diào)用的作為其參數(shù)傳入的λ表達(dá)式不直接在函數(shù)體中,而是執(zhí)行另一個上下文的λ表達(dá)式,例如一個局部對象或一個嵌套函數(shù)。這是,非局部返回在這種λ表達(dá)式將不能使用。為了說明這種情況,λ表達(dá)式參數(shù)需要以crossinline修飾符標(biāo)記:

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

break和continue在內(nèi)聯(lián)λ表達(dá)式中不允許使用,但我們正在計劃支持它們。

具體類型參數(shù)(Reified type parameters)

有時候我們需要訪問一個類型,這個類型作為參數(shù)傳遞給我們:

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

我們向上遍歷一棵樹并檢查每一個節(jié)點(diǎn)是否是指定類型。但方法的調(diào)用則不是很美觀直接:

treeNode.findParentOfType(MyTreeNode::class.java)

事實(shí)上我們僅想傳遞一個類型給這個函數(shù),就像這樣:

treeNode.findParentOfType<MyTreeNode>()

為了能夠這么做,內(nèi)聯(lián)函數(shù)支持具體化的類型參數(shù),因此我們可以這樣寫:

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

我們使用reified修飾符來修飾類型參數(shù),這時候可以在函數(shù)內(nèi)部訪問類型參數(shù),就像是一個普通的類。由于函數(shù)是內(nèi)聯(lián)的,不需要反射,常規(guī)操作符如!isas都可以正常使用。現(xiàn)在我們可以如下調(diào)用上個函數(shù):

myTree.findParentOfType<MyTreeNodeType>()

雖然在需要情況下不需要反射,我們?nèi)匀豢梢詫σ粋€具體化的類型參數(shù)使用它:

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

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

普通函數(shù)(未被inline標(biāo)記)不能有具體化參數(shù)。不具有運(yùn)行時表示的類型(例如非具體化的類型參數(shù)或類似于Nothing的虛構(gòu)類型)不能用作具體化的類型參數(shù)的實(shí)參。

內(nèi)聯(lián)屬性(Inline properties (since 1.1))

修飾符inline可以用于沒有后背字段的屬性的訪問器。可以單獨(dú)注解屬性訪問器:

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

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

也可以注解一個睡醒,將兩個訪問器都標(biāo)記為內(nèi)聯(lián):

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

在調(diào)用處,內(nèi)聯(lián)訪問將被作為常規(guī)內(nèi)聯(lián)函數(shù)一樣被內(nèi)聯(lián)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容