【kotlin】Inline functions (官方文件翻譯)
出處 https://kotlinlang.org/docs/inline-functions.html
使用高階函式會帶來某些執行時期的負擔:每個函式都是一個物件,而且會捕捉到閉包。閉包是可以在函式內部存取的變數範圍。記憶體配置(包括函式物件和類別的配置)以及虛擬呼叫都會引入執行時期的額外負擔。
但在許多情況下,這種額外負擔可以通過內聯化 lambda 表達式來消除。以下展示的函式就是這種情況的好例子。lock()
函式可以很容易地在呼叫點內聯化。請考慮以下情況:
lock(l) { foo() }
與其為參數創建一個函式物件並生成呼叫,編譯器可以發出以下程式碼:
l.lock()
try {
foo()
} finally {
l.unlock()
}
要讓編譯器這樣做,可以使用 inline
修飾符標記 lock()
函式:
inline fun <T> lock(lock: Lock, body: () -> T): T { ... }
inline
修飾符會影響函式本身和傳遞給它的 lambda 表達式:所有這些都會內聯到呼叫點。
內聯可能會導致生成的程式碼變大。然而,如果合理地進行內聯(避免內聯大型函式),在效能方面將會有顯著的提升,特別是在迴圈內的“多態性”呼叫點。
noinline
如果你不希望傳遞給內聯函式的所有 lambda 表達式都被內聯,可以使用 noinline
修飾符標記部分函式參數:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... }
可內聯的 lambda 表達式只能在內聯函式內被呼叫,或作為可內聯的參數傳遞。然而,使用 noinline
修飾符的 lambda 表達式則可以以任何方式操作,包括儲存在欄位中或傳遞。
@Suppress("NOTHING_TO_INLINE")
註解來抑制這個警告)。non-local returns 非本地返回
fun foo() {
ordinaryFunction {
return // ERROR: cannot make `foo` return here
}
}
fun foo() {
inlined {
return // OK: the lambda is inlined
}
}
這種在 lambda 內部但退出包含函式的返回稱為非本地返回。這種構造通常出現在循環中,而內聯函式通常會包裹這樣的循環。
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // returns from hasZeros
}
return false
}
注意,一些內聯函式可能會從不同的執行上下文(如局部物件或巢狀函式)而不是直接從函式主體內調用作為參數傳遞給它們的 lambda。在這種情況下,lambda 中也不允許非本地控制流。為了指示內聯函式的 lambda 參數不能使用非本地返回,可以使用 crossinline
修飾符來標記 lambda 參數:
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}
break
和 continue
在內聯 lambda 中目前還不可用,但我們計劃未來也支援它們。
Reified type parameters 具現化(reified)型別參數
有時你需要訪問作為參數傳遞的型別:
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>()
為了實現這一點,內聯函式支持具現化(reified)型別參數,因此你可以這樣寫:
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)型別參數來使用它:
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
普通函式(未標記為內聯)不能具有具現化(reified)參數。沒有運行時表示(例如非具現化的型別參數或類似 Nothing
的虛構型別)的型別不能作為具現化型別參數的參數。
Inline prperties
在 Kotlin 中,inline
修飾符確實可以用於沒有後端字段的屬性的訪問器上。你可以為個別的屬性訪問器添加註解:
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
你也可以對整個屬性進行註解,這將使其所有的訪問器都標記為內聯:
inline var bar: Bar
get() = ...
set(v) { ... }
在呼叫處,內聯訪問器會像普通的內聯函式一樣被內聯。
restrictions for public API inline functions
公共 API 的內聯函式限制
當內聯函式是公共或受保護的,但不屬於私有或內部聲明的一部分時,它被視為模組的公共 API。它可以在其他模組中被調用並且在這些調用點被內聯。然而,這會帶來二進制不兼容性的風險,如果調用模組在聲明模組變更後未重新編譯。
為了消除由模組的非公共 API 變更引入的這種不兼容性風險,公共 API 的內聯函式不允許在其內部使用非公共 API 的聲明(私有和內部)。
內部聲明可以使用 @PublishedApi
注解,這允許它在公共 API 的內聯函式中使用。當內部的內聯函式標記為 @PublishedApi
時,其函式體被視為公共,並且受到與公共函式相同的可見性限制和檢查。