Kotlin學習筆記8——內聯函數

前言

上一篇,我們學習了Kotlin中的高階函數和Lambda表達式,今天繼續來學習Kotlin中的函數。由於Kotlin中支持高階函數語法,所以函數我們分爲三篇來學習,今天是第三篇:內聯函數。

定義

被inline標記的函數就是內聯函數,示例:

public inline fun CharSequence.sumBy(selector: (Char) -> Int): Int {
    var sum: Int = 0
    for (element in this) {
        sum += selector(element)
    }
    return sum
}

內聯函數好處

調用一個方法其實就是一個方法壓棧和出棧的過程,調用方法時將棧幀壓入方法棧,然後執行方法體,方法結束時將棧幀出棧,這個壓棧和出棧的過程是一個耗費資源的過程,這個過程中傳遞形參也會耗費資源。我們在寫代碼的時候難免會遇到這種情況,就是很多處的代碼是一樣的,於是乎我們就會抽取出一個公共方法來進行調用,這樣看起來就會很簡潔;但是也出現了一個問題,就是這個方法會被頻繁調用,就會很耗費資源。請看示例:

fun <T> method(lock: Lock, body: () -> T): T {
        lock.lock()
        try {
            return body()
        } finally {
            lock.unlock()
        }
    }

這裏的method方法在調用的時候是不會把形參傳遞給其他方法的,調用一下:

 method(lock, {"我是body的方法體"})//lock是一個Lock對象

對於編譯器來說,調用method方法就要將參數lock和lambda表達式{“我是body的方法體”}進行傳遞,就要將method方法進行壓棧出棧處理,這個過程就會耗費資源。如果是很多地方調用,就會執行很多次,這樣就非常消耗資源了,於是乎就引進了inline。

inline關鍵字

那麼使用inline關鍵字標記函數後,在編譯時期,把調用這個函數的地方用這個函數的方法體進行替換。還是以上面的method函數爲例,再使用inline關鍵字標記method函數後,我們調用上面的method方法:

method(lock, {"我是body方法體"})//lock是一個Lock對象

其實上面調用的方法,在編譯時期就會把下面的內容替換到調用該方法的地方,這樣就會減少方法壓棧,出棧,進而減少資源消耗,從而提升了性能。

lock.lock()
try {
  return "我是body方法體"
} finally {
  lock.unlock()
}

noinline關鍵字

雖然內聯非常好用,但是會出現這麼一個問題,就是內聯函數的參數無法被內聯函數方法體中的其他非內聯函數調用。

inline fun <T> mehtod(lock: Lock, body: () -> T): T {
            lock.lock()
            try {
                otherMehtod(body)//會報錯,無法調用
                return body()
            } finally {
                lock.unlock()
            }
    }

    fun <T> otherMehtod(body: ()-> T){

    }

因爲method是內聯函數,所以它的形參body就是inline的,但是在編譯時期,body已經不是一個函數對象,而是一個具體的值,然而otherMehtod卻要接收一個body的函數對象,所以就編譯不通過了
解決方法:當然就是加noinline了,它的作用就已經非常明顯了.就是讓內聯函數的形參函數不是內聯的,保留原有的函數特徵。

看示例:

//body函數調用開銷大,需要被內聯,body1需要被內聯函數方法體中的其他非內聯函數調用,不需要內聯,用noinline 關鍵字修飾
inline fun <T> mehtod(lock: Lock, body: () -> T,noinline body1: () -> T): T {
    lock.lock()
    try {
        otherMehtod(body)
        return body1()
    } finally {
        lock.unlock()
    }
}

fun <T> otherMehtod(body: ()-> T){

}

crossinline關鍵字

crossinline 的作用是內聯函數中讓被標記爲crossinline 的lambda表達式不允許非局部返回。
首先我們來看下非局部返回
我們都知道,kotlin中,如果一個函數中,存在一個lambda表達式,在該lambda中不支持直接通過return退出該函數的,只能通過return@XXXinterface這種方式

fun outterFun() {
    innerFun {
        //return  //錯誤,不支持直接return
        //只支持通過標籤,返回innerFun
        return@innerFun 1
    }

    //如果是匿名或者具名函數,則支持
    var f = fun(){
        return
    }
}

fun innerFun(a: () -> Int) {}

但是如果這個函數是內聯的就可以返回

fun outterFun() {
    innerFun {
        return  //支持直接返回outterFun       
    }
}

inline fun innerFun(a: () -> Int) {}

這種直接從lambda中return掉函數的方法就是非局部返回,crossinline就是爲了讓其不能直接return。

fun outterFun() {
    innerFun {
        return  //這樣就報錯了
    }
}

inline fun innerFun( crossinline a: () -> Int) {}

這裏的a函數就是被crossinline修飾了,如果在lambda中直接return就無法編譯通過。說白了,我們如果直接在lambda參數中結束當前函數,而不給lambda提供一個返回值,這種情況是不被允許的。

reified關鍵字

reified,字面意思:具體化,其實就是具體化泛型。我們都知道在java中如果是泛型,是不能直接使用泛型的類型的,但是kotlin卻是可以的,這點和java就有了顯著的區別.通常java中解決的方案就是通過函數來傳遞類但是kotlin就老牛逼了,直接就可以用了,主要還是有內聯函數inline這個好東西,才使得kotlin能夠直接通過泛型就能拿到泛型的類型。

看示例:

 inline fun <reified T : Activity> Activity.startActivity() {
     startActivity(Intent(this, T::class.java))
}

通過kotlin的拓展寫個啓動activity的方法,只需要傳入該activity的泛型即可

startActivity<MainActivity>()

再來看看一個需求,有的時候我們需要創建一個Fragment的實例,並且要傳遞參數,如果是之前你可能會在每個Fragment裏面這樣寫:

 fun newInstance(param: Int): ActyFragment {
            val fragment = ActyFragment()
            val args = Bundle()
            args.putInt(PARAMS, param)
            fragment.arguments = args
            return fragment
        }

現在通過reified來優化:

inline fun <reified F : Fragment> Context.newFragment(vararg args: Pair<String, String>): F {
    val bundle = Bundle()
    args.let {
        for (arg in args) {
            bundle.putString(arg.first, arg.second)
        }
    }
    return Fragment.instantiate(this, F::class.java.name, bundle) as F
}

這樣就不要每個Fragment都寫個方法了,可以說非常的nice了。

尾巴

今天的學習筆記就先到這裏了,下一篇我們將學習Kotlin中的類和對象
老規矩,喜歡我的文章,歡迎素質三連:點贊,評論,關注,謝謝大家!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章