使用高階函數(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ī)操作符如!is
和as
都可以正常使用。現(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)。