kotlin函數和Lambda表達式——>內聯函數
1.內聯函數
使用高階函數會帶來一些運行時的效率損失:每一個函數都是一個對象,并且會捕獲一個閉包。即那些 在函數體內會訪問到的變量。內存分配(對于函數對象和類)和虛擬調用會引入運行時間開銷。
但是在許多情況下通過內聯化 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 表達式:所有這些都將內聯到調用處。
內聯可能導致生成的代碼增加;不過如果我們使用得當(即避免內聯過大函數),性能上會有所提升,尤 其是在循環中的“超多態(megamorphic)”調用處。
2.禁用內聯
如果希望只內聯一部分傳給內聯函數的 lambda 表達式參數,那么可以用 noinline 修飾符標記不希望內聯的函數參數
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ...... }
可以內聯的 lambda 表達式只能在內聯函數內部調用或者作為可內聯的參數傳遞,但是 noinline的可以以任何我們喜歡的方式操作:存儲在字段中、傳送它等等。
需要注意的是,如果一個內聯函數沒有可內聯的函數參數并且沒有具體化的類型參數,編譯器會產生一 個警告,因為內聯這樣的函數很可能并無益處(如果你確認需要內聯,則可以用@Suppress("NOTHING_TO_INLINE") 注解關掉該警告)。
非局部返回
在 Kotlin 中,我們只能對具名或匿名函數使用正常的、非限定的 return 來退出。這意味著要退出一個 lambda表達式,我們必須使用一個標簽,并且在lambda表達式內部禁止使用裸 return,因為lambda 表達式不能使包含它的函數返回:
fun foo() { ordinaryFunction { return // 錯誤:不能使 `foo` 在此處返回 } }
但是如果 lambda 表達式傳給的函數是內聯的,該 return 也可以內聯,所以它是允許的: inline fun inlined(block: () -> Unit) { println("hi!") }
fun foo() { inlined { 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 表達式中還不可用,但我們也計劃支持它們。
3.具體化的類型參數
有時候我們需要訪問一個作為參數傳給我們的一個類型:
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 的虛構類型)不能用作具體化的類型參數的實參。
4.內聯屬性(自1.1起)
inline 修飾符可用于沒有幕后字段的屬性的訪問器。你可以標注獨立的屬性訪問器:
val foo: Foo inline get() = Foo() var bar: Bar get() = ...... inline set(v) { ...... }
你也可以標注整個屬性,將它的兩個訪問器都標記為內聯
inline var bar: Bar get() = ...... set(v) { ...... }
在調用處,內聯訪問器如同內聯函數一樣內聯
5.公有API內聯函數的限制
當一個內聯函數是 public 或 protected 而不是 private 或 internal 聲明的一部分時,就會 認為它是一個模塊級的公有 API。可以在其他模塊中調用它,并且也可以在調用處內聯這樣的調用。
這帶來了一些由模塊做這樣變更時導致的二進制兼容的?險?聲明一個內聯函數但調用它的模塊在 它修改后并沒有重新編譯。
為了消除這種由非公有 API 變更引入的不兼容的?險,公有 API 內聯函數體內不允許使用非公有聲明, 即,不允許使用 private 與 internal 聲明以及其部件。
一個 internal 聲明可以由 @PublishedApi 標注,這會允許它在公有 API 內聯函數中使用。當一 個 internal 內聯函數標記有 @PublishedApi 時,也會像公有函數一樣檢測其函數體。

浙公網安備 33010602011771號