天天看點

Kotlin-23.内聯函數(Inline Functions)

官方文檔: http://kotlinlang.org/docs/reference/inline-functions.html

1.内聯函數的概念和作用

使用高階函數(higher-order functions)會導緻一些性能的損耗:
    每個函數都是對象,且會捕獲閉包closure(即變量會在函數體内被通路),
    函數對象/類會增加記憶體配置設定,而且虛拟調用棧也會增加額外記憶體開銷!

可用内聯函數(inline function)消除這些額外記憶體開銷,
說白了就是在調用處插入函數體代碼,以此減少建立函數棧和對象的記憶體開銷!   
被inline修飾的函數或lambda表達式,在調用時都會被内聯(在調用處插入函數體代碼)
    inline fun lock<T>(lock: Lock, body: () -> T): T {
        lock.lock()
        try {
            return body()
        }
        finally {
            lock.unlock()
        }        
    }

    print("開始************")
    lock(l) { foo() }
    print("結束************")

    //編譯器實際生成以下代碼(就是直接把代碼插入到調用處):     
    print("開始************")
    l.lock()
    try {
        foo()
    }
    finally {
        l.unlock()
    }
    print("結束************")

很明顯,内聯可能導緻編譯器生成的代碼增加,但如果使用得當(不内聯大函數),在性能上有很大提升,
尤其是在循環的megamorphic處調用!

禁用内聯(noinline)
如果内聯函數的有些(作為參數)lambda表達式不是内聯,可用noinline修飾符函數參數!
    inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
        // ……
    }
           

2.非局部傳回(Non-local returns)

在内聯的lambda表達式中退出包含它的函數稱為非局部傳回(Non-local returns)!
在Kotlin中,正常return(沒有限定符@)表示退出一個命名或匿名函數
是以要退出lambda表達式,return需要加限定符@标簽,
在非内聯的lambda表達式中禁用正常return(沒有限定符@):
    fun f1(p: ()->Unit) {
        p()
    }

    inline fun f2(p: ()->Unit){
        p()
    }

    fun main(args: Array<String>) {
        f1{
            //f2和lambda表達式都不是内聯
            println("Hello, world!")
            return //編譯錯誤,此處不允許return
        }

        f2{
            //f2是内聯函數,lambda表達式也是内聯
            println("Hello, world!")
            return //編譯正确,可以return
        }

        listOf(1,2,3).forEach {
            //forEach是内聯
            if(it==2) return //編譯正确,可以return
            print(it)     
        }
    }

此外,一些内聯函數參數不是直接來自函數體,而是來自另一個上下文的lambda表達式參數,
例如來自局部對象或嵌套函數,lambda表達式也不允許非局部傳回!
為了辨別這種情況,該lambda表達式參數需要用crossinline修飾符:
    inline fun f(crossinline body: () -> Unit) {
        val f = object: Runnable {
            override fun run() = body()
        }           
    }

目前,break和continue在内聯的lambda表達式中還不可用,未來kotlin計劃支援!
           

3.類型參數的具體化(Reified type parameters)

執行個體(類型參數-泛型):
    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)

1.為簡化函數調用,内聯函數支援類型參數具體化:
    inline fun <reified T> TreeNode.findParentOfType(): T? {
        //用reified修飾符類型參數T,可在函數内通路T,像通路普通類,
        //由于函數是内聯的,無需反射,正常操作符如!is和as都能用,
        var p = parent
        while (p != null && p !is T) {
            p = p.parent
        }
        return p as T?
    }
    //該函數調用簡潔優雅
    treeNode.findParentOfType<MyTreeNode>()

2.雖然多數情況不需要反射,但仍然可對具體化的類型參數使用:
    inline fun <reified T> membersOf() = T::class.members
    fun main(s: Array<String>) {
        println(membersOf<StringBuilder>().joinToString("\n"))
    }

3.泛型的具體化條件
    普通函數(未标記為内聯函數)不能具體化參數!
    在運作時無法表示的類型(類似Nothing虛構類型)不能作為具體化參數的實參!
           

4.内聯屬性(Inline properties)

自kotlin 1.1起, inline可修飾[沒有幕後字段]屬性通路器get/set函數(方法)
在調用處,内聯通路器get/set函數,和内聯函數一樣内聯,沒什麼差別!
1.修飾單個屬性通路器:
    val foo: Foo
        inline get() = Foo()

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

2.修飾整個屬性,将兩個通路器都标記為内聯:
    inline var bar: Bar
        get() = ……
        set(v) { …… }
           

簡書:http://www.jianshu.com/p/79396a5056d7

CSDN部落格: http://blog.csdn.net/qq_32115439/article/details/73929039

GitHub部落格:http://lioil.win/2017/06/29/Kotlin-inline-fun.html

Coding部落格:http://c.lioil.win/2017/06/29/Kotlin-inline-fun.html