# 內聯函數
使用[高階函數](lambdas.html)在運行時也會帶來確定的不利因素:每個函數都是一個對象,而且它捕獲一個閉包,意即那些變量在函數主體內部被訪問。運行時會帶來額外的內存分配(對函數對象和類兩者)和有效調用。
但在一些場景看來這種額外開銷可以通過內聯 lambda 表達式避免。前文所示的函數是這種場景很好的例子。即,`lock()` 函數可以簡單地內聯在調用場所。思考下面的情況:
``` kotlin
lock(l) { foo() }
```
代替為參數創建函數對象并生成調用,編譯器能夠生成如下代碼
``` kotlin
l.lock()
try {
foo()
}
finally {
l.unlock()
}
```
這不是我們從一開始就想要的嗎?
要讓編譯器做到這個,我們需要和 `inline` 修飾符一起來標記 `lock()` 函數:
``` kotlin
inline fun lock<T>(lock: Lock, body: () -> T): T {
// ...
}
```
`inline` 標識符影響函數本身和傳遞給它的 lambda:它們全部都會被內聯到調用場所中。
內聯會使得生成的代碼增加,但如果我們采用合理的方式(不要內聯大的函數)它將在性能上有所回報,尤其是在循環內的 “megamorphic(超級形態??)” 的調用場景。
## noinline
萬一你只要傳遞到內聯函數的 lambda 其中的一些被內聯,你可以用 `noinline` 修飾符標記你的函數參數其中的這些:
``` kotlin
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ...
}
```
可內聯的 lambda 只能在內聯函數內部調用或作為可內聯的實參傳遞,但 `noinline` 的能夠以我們喜歡的任何方式操作:存儲在字段中、在周圍傳遞等等。
注意如果一個內聯函數沒有可內聯的函數參數并且沒有[具體化類型的參數](#reified-type-parameters),編譯器將發出一個警告,說明內聯這樣的函數沒有好處(如果你確定需要這樣內聯,你可以禁止這個警告)。
## 非局部返回
Kotlin 中我們只能使用一個普通、無限制的 `return` 來退出一個已命名的函數或匿名函數。這個意思是指要退出一個 lambda,我們就得使用一個[標簽](returns.html#return-at-labels),而且在 lambda 里面禁止裸露的 `return`,因為一個 lambda 不能讓封閉的函數返回:
``` kotlin
fun foo() {
ordinaryFunction {
return // 錯誤:不能讓 foo 從這返回
}
}
```
但如果這個函數傳遞過來的 lambda 已被內聯,那么返回就能很好地被內聯。因此它允許這樣:
``` kotlin
fun foo() {
inlineFunction {
return // OK: lambda 已經內聯
}
}
```
這樣的返回(位于 lambda 里面,但退出封閉的函數)被稱作*非局部*返回。我們經常用內聯函數嵌入這種循環構造:
``` kotlin
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // 從 hasZeros 返回
}
return false
}
```
注意一些內聯函數可以從其它執行上下文像參數一樣訪問傳遞給它們的 lambda 而不是直接從函數主體調用,就像一個局部對象或嵌入函數一樣。對于這種情況,lambda 內非局部控制流仍然不被允許。要指出這一點,lambda 參數需要以 `crossinline` 修飾符標記:
``` kotlin
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}
```
> `break` 和 `continue` 在內聯 lambda 中不再有效,但我們也有計劃支持它們
## 具體化類型參數
有時我們需要方法傳遞一個類型作為參數給我們:
``` kotlin
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
}
```
這里,我們沿著一顆樹并使用返回來檢查一個節點是否有確定的類型。其它的都很好,只是調用場所不太美觀:
``` kotlin
myTree.findParentOfType(MyTreeNodeType::class.java)
```
事實我們想要簡單地傳遞一個類型到這個函數,意即像這樣調用它:
``` kotlin
myTree.findParentOfType<MyTreeNodeType>()
```
為了能這樣,內聯函數支持*具體化類型參數*,因此我們能夠寫下像這樣的東西:
``` kotlin
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>()`。
盡管反射在一些情況下并不被需要,我們仍然能夠與一個具體化類型參數一起使用它:
``` kotlin
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
```
普通函數(沒有被標記為 `inline`)不能有具體化的參數。一個類型沒有一個運行時代理(例如,一個非具體化類型參數或一個類似 `Nothing` 的假想類型)不能像一個具體化類型參數的實參那樣使用。
更核心的描述查看[特別文檔](https://github.com/JetBrains/kotlin/blob/master/spec-docs/reified-type-parameters.md)。