使用高階函數(shù)會(huì)帶來(lái)一些運(yùn)行時(shí)的效率損失:每一個(gè)函數(shù)都是一個(gè)對(duì)象,并且會(huì)捕獲一個(gè)閉包。
即那些在函數(shù)體內(nèi)會(huì)訪問(wèn)到的變量。
內(nèi)存分配(對(duì)于函數(shù)對(duì)象和類)和虛擬調(diào)用會(huì)引入運(yùn)行時(shí)間開銷。
但是在許多情況下通過(guò)內(nèi)聯(lián)化 lambda 表達(dá)式可以消除這類的開銷。
下述函數(shù)是這種情況的很好的例子。即 lock() 函數(shù)可以很容易地在調(diào)用處內(nèi)聯(lián)。
考慮下面的情況:
lock(l) { foo() }
編譯器沒(méi)有為參數(shù)創(chuàng)建一個(gè)函數(shù)對(duì)象并生成一個(gè)調(diào)用。取而代之,編譯器可以生成以下代碼:
l.lock()
try {
foo()
}
finally {
l.unlock()
}
這個(gè)不是我們從一開始就想要的嗎?
為了讓編譯器這么做,我們需要使用 inline 修飾符標(biāo)記 lock() 函數(shù):
inline fun lock<T>(lock: Lock, body: () -> T): T {
// ……
}
inline 修飾符影響函數(shù)本身和傳給它的 lambda 表達(dá)式:所有這些都將內(nèi)聯(lián)
到調(diào)用處。
內(nèi)聯(lián)可能導(dǎo)致生成的代碼增加,但是如果我們使用得當(dāng)(不內(nèi)聯(lián)大函數(shù)),它將在
性能上有所提升,尤其是在循環(huán)中的“超多態(tài)(megamorphic)”調(diào)用處。
如果你只想被(作為參數(shù))傳給一個(gè)內(nèi)聯(lián)函數(shù)的 lamda 表達(dá)式中只有一些被內(nèi)聯(lián),你可以用 noinline 修飾符標(biāo)記
一些函數(shù)參數(shù):
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ……
}
可以內(nèi)聯(lián)的 lambda 表達(dá)式只能在內(nèi)聯(lián)函數(shù)內(nèi)部調(diào)用或者作為可內(nèi)聯(lián)的參數(shù)傳遞,
但是 noinline 的可以以任何我們喜歡的方式操作:存儲(chǔ)在字段中、傳送它等等。
需要注意的是,如果一個(gè)內(nèi)聯(lián)函數(shù)沒(méi)有可內(nèi)聯(lián)的函數(shù)參數(shù)并且沒(méi)有
具體化的類型參數(shù),編譯器會(huì)產(chǎn)生一個(gè)警告,因?yàn)閮?nèi)聯(lián)這樣的函數(shù)
很可能并無(wú)益處(如果你確認(rèn)需要內(nèi)聯(lián),則可以關(guān)掉該警告)。
在 Kotlin 中,我們可以只使用一個(gè)正常的、非限定的 return 來(lái)退出一個(gè)命名或匿名函數(shù)。
這意味著要退出一個(gè) lambda 表達(dá)式,我們必須使用一個(gè)標(biāo)簽,并且
在 lambda 表達(dá)式內(nèi)部禁止使用裸 return,因?yàn)?lambda 表達(dá)式不能使包含它的函數(shù)返回:
fun foo() {
ordinaryFunction {
return // 錯(cuò)誤:不能使 `foo` 在此處返回
}
}
但是如果 lambda 表達(dá)式傳給的函數(shù)是內(nèi)聯(lián)的,該 return 也可以內(nèi)聯(lián),所以它是允許的:
fun foo() {
inlineFunction {
return // OK:該 lambda 表達(dá)式是內(nèi)聯(lián)的
}
}
這種返回(位于 lambda 表達(dá)式中,但退出包含它的函數(shù))稱為非局部返回。 我們習(xí)慣了
在循環(huán)中用這種結(jié)構(gòu),其內(nèi)聯(lián)函數(shù)通常包含:
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // 從 hasZeros 返回
}
return false
}
請(qǐng)注意,一些內(nèi)聯(lián)函數(shù)可能調(diào)用傳給它們的不是直接來(lái)自函數(shù)體、而是來(lái)自另一個(gè)執(zhí)行
上下文的 lambda 表達(dá)式參數(shù),例如來(lái)自局部對(duì)象或嵌套函數(shù)。在這種情況下,該 lambda 表達(dá)式中
也不允許非局部控制流。為了標(biāo)識(shí)這種情況,該 lambda 表達(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)的 lambda 表達(dá)式中還不可用,但我們也計(jì)劃支持它們
有時(shí)候我們需要訪問(wèn)一個(gè)作為參數(shù)傳給我們的一個(gè)類型:
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?
}
在這里我們向上遍歷一棵樹并且檢查每個(gè)節(jié)點(diǎn)是不是特定的類型。
這都沒(méi)有問(wèn)題,但是調(diào)用處不是很優(yōu)雅:
treeNode.findParentOfType(MyTreeNode::class.java)
我們真正想要的只是傳一個(gè)類型給該函數(shù),即像這樣調(diào)用它:
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 修飾符來(lái)限定類型參數(shù),現(xiàn)在可以在函數(shù)內(nèi)部訪問(wèn)它了,
幾乎就像是一個(gè)普通的類一樣。由于函數(shù)是內(nèi)聯(lián)的,不需要反射,正常的操作符如 !is
和 as 現(xiàn)在都能用了。此外,我們還可以按照上面提到的方式調(diào)用它:myTree.findParentOfType<MyTreeNodeType>()。
雖然在許多情況下可能不需要反射,但我們?nèi)匀豢梢詫?duì)一個(gè)具體化的類型參數(shù)使用它:
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
普通的函數(shù)(未標(biāo)記為內(nèi)聯(lián)函數(shù)的)不能有具體化參數(shù)。
不具有運(yùn)行時(shí)表示的類型(例如非具體化的類型參數(shù)或者類似于Nothing的虛構(gòu)類型)
不能用作具體化的類型參數(shù)的實(shí)參。
相關(guān)底層描述,請(qǐng)參見(jiàn)規(guī)范文檔。
inline 修飾符可用于沒(méi)有幕后字段的屬性的訪問(wèn)器。
你可以標(biāo)注獨(dú)立的屬性訪問(wèn)器:
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ……
inline set(v) { …… }
你也可以標(biāo)注整個(gè)屬性,將它的兩個(gè)訪問(wèn)器都標(biāo)記為內(nèi)聯(lián):
inline var bar: Bar
get() = ……
set(v) { …… }
在調(diào)用處,內(nèi)聯(lián)訪問(wèn)器如同內(nèi)聯(lián)函數(shù)一樣內(nèi)聯(lián)。