# 【kotlin】Inline functions (官方文件翻譯)

出處 [https://kotlinlang.org/docs/inline-functions.html](https://kotlinlang.org/docs/inline-functions.html)

使用高階函式會帶來某些執行時期的負擔：每個函式都是一個物件，而且會捕捉到閉包。閉包是可以在函式內部存取的變數範圍。記憶體配置（包括函式物件和類別的配置）以及虛擬呼叫都會引入執行時期的額外負擔。

但在許多情況下，這種額外負擔可以通過內聯化 lambda 表達式來消除。以下展示的函式就是這種情況的好例子。`lock()` 函式可以很容易地在呼叫點內聯化。請考慮以下情況：

```JavaScript
lock(l) { foo() }
```

與其為參數創建一個函式物件並生成呼叫，編譯器可以發出以下程式碼：

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

要讓編譯器這樣做，可以使用 `inline` 修飾符標記 `lock()` 函式：

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

`inline` 修飾符會影響函式本身和傳遞給它的 lambda 表達式：所有這些都會內聯到呼叫點。

內聯可能會導致生成的程式碼變大。然而，如果合理地進行內聯（避免內聯大型函式），在效能方面將會有顯著的提升，特別是在迴圈內的“多態性”呼叫點。

#### noinline

如果你不希望傳遞給內聯函式的所有 lambda 表達式都被內聯，可以使用 `noinline` 修飾符標記部分函式參數：

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

可內聯的 lambda 表達式只能在內聯函式內被呼叫，或作為可內聯的參數傳遞。然而，使用 `noinline` 修飾符的 lambda 表達式則可以以任何方式操作，包括儲存在欄位中或傳遞。<svg fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>

<div id="bkmrk-%E5%A6%82%E6%9E%9C%E4%B8%80%E5%80%8B%E5%85%A7%E8%81%AF%E5%87%BD%E5%BC%8F%E6%B2%92%E6%9C%89%E5%8F%AF%E5%85%A7%E8%81%AF%E7%9A%84%E5%87%BD%E5%BC%8F%E5%8F%83%E6%95%B8%E4%B8%94%E6%B2%92"><div><div><div><div><span class="overflow-hidden text-clip whitespace-nowrap text-sm">如果一個內聯函式沒有可內聯的函式參數且沒有具現化的型別參數，編譯器會發出警告，因為內聯這樣的函式不太可能有任何好處（如果你確定需要內聯，可以使用 `@Suppress("NOTHING_TO_INLINE")` 註解來抑制這個警告）。</span></div><div></div></div></div></div></div>#### non-local returns 非本地返回

<div id="bkmrk-%E5%9C%A8-kotlin-%E4%B8%AD%EF%BC%8C%E4%BD%A0%E5%8F%AA%E8%83%BD%E5%9C%A8%E5%91%BD%E5%90%8D%E5%87%BD%E5%BC%8F%E6%88%96"><div><div></div><div><span class="overflow-hidden text-clip whitespace-nowrap text-sm">在 Kotlin 中，你只能在命名函式或匿名函式中使用普通的未修飾返回來退出。要退出 lambda，則需要使用標籤。在 lambda 內部禁止使用裸的返回，因為 lambda 不能使包含它的函式返回。</span></div></div></div><div id="bkmrk-"></div>```JavaScript
fun foo() {
    ordinaryFunction {
        return // ERROR: cannot make `foo` return here
    }
}
```

<div id="bkmrk--0"></div><div id="bkmrk-%E4%BD%86%E6%98%AF%EF%BC%8C%E5%A6%82%E6%9E%9C%E5%82%B3%E9%81%9E-lambda-%E7%9A%84%E5%87%BD%E5%BC%8F%E8%A2%AB%E5%85%A7">但是，如果傳遞 lambda 的函式被內聯，則返回語句也會被內聯，因此這樣做是允許的：</div>```shell
fun foo() {
    inlined {
        return // OK: the lambda is inlined
    }
}
```

這種在 lambda 內部但退出包含函式的返回稱為非本地返回。這種構造通常出現在循環中，而內聯函式通常會包裹這樣的循環。

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

注意，一些內聯函式可能會從不同的執行上下文（如局部物件或巢狀函式）而不是直接從函式主體內調用作為參數傳遞給它們的 lambda。在這種情況下，lambda 中也不允許非本地控制流。為了指示內聯函式的 lambda 參數不能使用非本地返回，可以使用 `crossinline` 修飾符來標記 lambda 參數：

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

`break` 和 `continue` 在內聯 lambda 中目前還不可用，但我們計劃未來也支援它們。

#### Reified type parameters 具現化（reified）型別參數 

有時你需要訪問作為參數傳遞的型別：

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

這裡，你遍歷一棵樹並使用反射來檢查節點是否具有某種類型。這一切都很好，但呼叫點並不十分美觀：

```JavaScript
treeNode.findParentOfType(MyTreeNode::class.java)
```

更好的解決方案是直接將型別作為參數傳遞給這個函式。你可以這樣調用它：

```
treeNode.findParentOfType<MyTreeNode>()
```

為了實現這一點，內聯函式支持具現化（reified）型別參數，因此你可以這樣寫：

```JavaScript
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>()`。

雖然在許多情況下不需要使用反射，但你仍然可以使用具現化（reified）型別參數來使用它：

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

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

普通函式（未標記為內聯）不能具有具現化（reified）參數。沒有運行時表示（例如非具現化的型別參數或類似 `Nothing` 的虛構型別）的型別不能作為具現化型別參數的參數。

#### Inline prperties

在 Kotlin 中，`inline` 修飾符確實可以用於沒有後端字段的屬性的訪問器上。你可以為個別的屬性訪問器添加註解：

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

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

你也可以對整個屬性進行註解，這將使其所有的訪問器都標記為內聯：

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

在呼叫處，內聯訪問器會像普通的內聯函式一樣被內聯。

#### restrictions for public API inline functions   
公共 API 的內聯函式限制

當內聯函式是公共或受保護的，但不屬於私有或內部聲明的一部分時，它被視為模組的公共 API。它可以在其他模組中被調用並且在這些調用點被內聯。然而，這會帶來二進制不兼容性的風險，如果調用模組在聲明模組變更後未重新編譯。

為了消除由模組的非公共 API 變更引入的這種不兼容性風險，公共 API 的內聯函式不允許在其內部使用非公共 API 的聲明（私有和內部）。

內部聲明可以使用 `@PublishedApi` 注解，這允許它在公共 API 的內聯函式中使用。當內部的內聯函式標記為 `@PublishedApi` 時，其函式體被視為公共，並且受到與公共函式相同的可見性限制和檢查。

<div id="bkmrk--1"></div>