Kotlin基礎以內聯函數

內聯函數

使用高階函數會給運行時帶來一些壞處:每一個函數都是一個對象,捕獲閉包(如:訪問函數體內的變量),內存分配(函數對象或Class),虛擬調用引入的運行過載。 使用內聯Lambda表達式在多數狀況下能夠消除這種過載。好比下面的函數就是這種狀況下的很好的例子,lock()函數能夠很容易地在調用點進行內聯擴展。java

lock(l){ foo() }

編譯可以產生下面的代碼,而不是建立一個函數對象參數,生成調用。閉包

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

也是咱們一開始想要的。 爲了讓編譯器可以這樣執行,須要用inline修飾符來標記lock函數。ide

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

inline修飾符既影響函數對象自己,也影響傳入的Lambda參數:二者都會被內聯到調用點。函數

編譯預處理器對內聯函數進行擴展,省去了參數壓棧、生成彙編語言的CALL調用、返回參數、執行return等過程,從而提升了運行速度。 使用內聯函數的優勢,在函數被內聯後編譯器就能夠經過上下文相關的優化技術對結果代碼執行更深刻的優化。 內聯不是萬能藥,它以代碼膨脹爲代價,僅僅省去了函數調用的開銷,從而提升程序的執行效率。優化

說明:函數調用開銷並不包括執行函數體所須要的開銷,而是僅指參數壓棧、跳轉、退棧和返回等操做。若是執行函數體內代碼的時間比函數調用的開銷大得多,那麼內聯函數的效率收益會笑不少。另外一方面每一處內聯函數的調用都要拷貝代碼,將使程序的總代碼增大、消耗更多的內存空間。ui

noinline

若是隻須要在內聯函數中內聯部分Lambda表達式,能夠使用noinline來標記不須要內聯的參數。spa

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

內聯Lambda只能在內聯函數中調用或做爲內聯參數,但noinline的Lambda可隨意使用。
說明:沒有內聯函數參數和reified type parameters的內聯函數,編譯器會發出警告,由於內聯這樣的函數不見得有好處。code

非局部返回

在Kotlin中能夠使用正常、無條件的return退出有名和匿名函數,也意味須要使用一個標籤來退出Lambda,在Lambda中禁止使用赤裸return語句,由於Lambda不可以使閉合函數返回。對象

fun foo(){
    ordinaryFunction{
        
        return // ERROR: can not make `foo` return here
    }
}

若是Lambda傳入內聯函數,則返回也是被內聯,因此被容許。內存

fun foo(){
    inlineFunction {
        return // OK: the lambda is inlined
    }
}

這樣的return(位於在Lambda中,但可以退出閉合函數)被稱爲非局部返回。Kotlin使用這種構造在有循環條件的閉合內聯函數中。

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

一些內聯函數可能不是從函數體中直接調用傳入的Lambda參數,而是從其餘的執行上下文,如本地對象或嵌套函數。在這些狀況下,non-local 控制流則不容許出如今Lambda中。使用crossinline修飾符來標記。

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

具體化類型參數

有時須要訪問傳入函數中參數的類型。例如:

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修飾符限制參數類型,能夠在內聯函數中訪問,就像是普通的Class。由於函數是內聯的,不在須要反射,像!is和as的普通操做符執行。也能夠像上述說的那樣調用。

myTree.findParentOfType<MyTreeNodeType>()

儘管反射在不少狀況不須要,仍須要使用它來具體話參數類型。

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

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

內聯屬性

inline修飾符能夠用在沒有Backing Filed屬性的訪問函數。能夠註解單獨屬性的訪問函數。

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

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

甚至能夠註解整個屬性,讓屬性訪問函數都變爲內聯函數。

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

在調用時,內聯訪問函數與常規內聯函數調用方式同樣。

閱讀原文

http://click.aliyun.com/m/39909/

相關文章
相關標籤/搜索