Kotlin內聯函數
內聯函數
使用高階函數會帶來一些運行時的效率損失:每一個函數都是一個對象,並且會捕獲一個閉包。
即那些在函數體內會訪問到的變量。
內存分配(對於函數對象和類)和虛擬調用會引入運行時間開銷。
但是在許多情況下通過內聯化 lambda 表達式可以消除這類的開銷。
下述函數是這種情況的很好的例子。即 lock()
函數可以很容易地在調用處內聯。
考慮下面的情況:
lock(l) { foo() }
編譯器沒有爲參數創建一個函數對象並生成一個調用。取而代之,編譯器可以生成以下代碼:
l.lock()
try {
foo()
}
finally {
l.unlock()
}
這個不是我們從一開始就想要的嗎?
爲了讓編譯器這麼做,我們需要使用 inline
修飾符標記 lock()
函數:
inline fun lock<T>(lock: Lock, body: () -> T): T {
// ……
}
inline
修飾符影響函數本身和傳給它的 lambda 表達式:所有這些都將內聯
到調用處。
內聯可能導致生成的代碼增加,但是如果我們使用得當(不內聯大函數),它將在
性能上有所提升,尤其是在循環中的「超多態(megamorphic)」調用處。
禁用內聯
如果你只想被(作爲參數)傳給一個內聯函數的 lamda 表達式中只有一些被內聯,你可以用 noinline
修飾符標記
一些函數參數:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ……
}
可以內聯的 lambda 表達式只能在內聯函數內部調用或者作爲可內聯的參數傳遞,
但是 noinline
的可以以任何我們喜歡的方式操作:存儲在字段中、傳送它等等。
需要注意的是,如果一個內聯函數沒有可內聯的函數參數並且沒有
具體化的類型參數,編譯器會產生一個警告,因爲內聯這樣的函數
很可能並無益處(如果你確認需要內聯,則可以關掉該警告)。
非局部返回
在 Kotlin 中,我們可以只使用一個正常的、非限定的 return
來退出一個命名或匿名函數。
這意味着要退出一個 lambda 表達式,我們必須使用一個標籤,並且
在 lambda 表達式內部禁止使用裸 return
,因爲 lambda 表達式不能使包含它的函數返回:
fun foo() {
ordinaryFunction {
return // 錯誤:不能使 `foo` 在此處返回
}
}
但是如果 lambda 表達式傳給的函數是內聯的,該 return 也可以內聯,所以它是允許的:
fun foo() {
inlineFunction {
return // OK:該 lambda 表達式是內聯的
}
}
這種返回(位於 lambda 表達式中,但退出包含它的函數)稱爲非局部返回。 我們習慣了
在循環中用這種結構,其內聯函數通常包含:
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // 從 hasZeros 返回
}
return false
}
請注意,一些內聯函數可能調用傳給它們的不是直接來自函數體、而是來自另一個執行
上下文的 lambda 表達式參數,例如來自局部對象或嵌套函數。在這種情況下,該 lambda 表達式中
也不允許非局部控制流。爲了標識這種情況,該 lambda 表達式參數需要
用 crossinline
修飾符標記:
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ……
}
break
和continue
在內聯的 lambda 表達式中還不可用,但我們也計劃支持它們
具體化的類型參數
有時候我們需要訪問一個作爲參數傳給我們的一個類型:
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
修飾符來限定類型參數,現在可以在函數內部訪問它了,
幾乎就像是一個普通的類一樣。由於函數是內聯的,不需要反射,正常的操作符如 !is
和 as
現在都能用了。此外,我們還可以按照上面提到的方式調用它:myTree.findParentOfType<MyTreeNodeType>()
。
雖然在許多情況下可能不需要反射,但我們仍然可以對一個具體化的類型參數使用它:
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
普通的函數(未標記爲內聯函數的)不能有具體化參數。
不具有運行時表示的類型(例如非具體化的類型參數或者類似於Nothing
的虛構類型)
不能用作具體化的類型參數的實參。
相關底層描述,請參見規範文檔。
內聯屬性(自 1.1 起)
inline
修飾符可用於沒有幕後字段的屬性的訪問器。
你可以標註獨立的屬性訪問器:
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ……
inline set(v) { …… }
你也可以標註整個屬性,將它的兩個訪問器都標記爲內聯:
inline var bar: Bar
get() = ……
set(v) { …… }
在調用處,內聯訪問器如同內聯函數一樣內聯。