Kotlin內(nèi)聯(lián)函數(shù)

Kotlin里使用關(guān)鍵 inline 來表示內(nèi)聯(lián)函數(shù),那么到底什么是內(nèi)聯(lián)函數(shù)呢脯丝,內(nèi)聯(lián)函數(shù)有什么好處呢?

1. 什么是內(nèi)聯(lián)inline伏伐?

在 Java 里是沒有內(nèi)聯(lián)這個(gè)概念的宠进,所有的函數(shù)調(diào)用都是普通方法調(diào)用,如果了解 Java 虛擬機(jī)原理的藐翎,可以知道 Java 方法執(zhí)行的內(nèi)存模型是基于 Java 虛擬機(jī)棧的:每個(gè)方法被執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)材蹬,用于存儲(chǔ)局部變量表实幕、操作數(shù)棧、動(dòng)態(tài)鏈接堤器、方法出口等信息昆庇。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個(gè)棧幀入棧闸溃、出棧的過程整吆。

也就是說每調(diào)用一個(gè)方法,都會(huì)對應(yīng)一個(gè)棧幀的入棧出棧過程辉川,如果你有一個(gè)工具類方法表蝙,在某個(gè)循環(huán)里調(diào)用很多次,那就會(huì)對應(yīng)很多次的棧幀入棧乓旗、出棧過程府蛇。這里首先要記住的一點(diǎn)是,棧幀的創(chuàng)建及入棧屿愚、出棧都是有性能損耗的汇跨。下面以一個(gè)例子來說明,看段代碼片段:


fun test() {
    //多次調(diào)用 sum() 方法進(jìn)行求和運(yùn)算
    println(sum(1, 2, 3))
    println(sum(100, 200, 300))
    println(sum(12, 34))
    //....可能還有若干次
}

/**
 * 求和計(jì)算
  */
fun sum(vararg ints: Int): Int {
    var sum = 0
    for (i in ints) {
        sum += i
    }
    return sum
}

在測試方法 test() 里妆距,我們多次調(diào)用了 sum() 方法穷遂。為了避免多次調(diào)用 sum() 方法帶來的性能損耗,我們期望的代碼類似這樣子的:

fun test() {
    var sum = 0
    for (i in arrayOf(1, 2, 3)) {
        sum += i            
    }
    println(sum)
    
    sum = 0
    for (i in arrayOf(100, 200, 300)) {
        sum += i            
    }
    println(sum)
    
    sum = 0
    for (i in arrayOf(12, 34)) {
        sum += i            
    }
    println(sum)
}

3次數(shù)據(jù)求和操作毅厚,都是在 test() 方法里執(zhí)行的塞颁,沒有之前的 sum() 方法調(diào)用,最后的結(jié)果依然是一樣的吸耿,但是由于減少了方法調(diào)用祠锣,雖然代碼量增加了,但是性能確提升了咽安。那么怎么實(shí)現(xiàn)這種情況呢伴网,一般工具類有很多公共方法,我總不能在需要調(diào)用這些公共方法的地方妆棒,把代碼復(fù)制一遍吧澡腾,內(nèi)聯(lián)就是為了解決這一問題。

定義內(nèi)聯(lián)函數(shù):

inline fun sum(vararg ints: Int): Int {
    var sum = 0
    for (i in ints) {
        sum += i
    }
    return sum
}

如上所示糕珊,用關(guān)鍵字 inline 標(biāo)記函數(shù)动分,該函數(shù)就是一個(gè)內(nèi)聯(lián)函數(shù)。還是原來的 test() 方法红选,編譯器在編譯的時(shí)候澜公,會(huì)自動(dòng)把內(nèi)聯(lián)函數(shù) sum() 方法體內(nèi)的代碼,替換到調(diào)用該方法的地方喇肋。查看編譯后的字節(jié)碼坟乾,會(huì)發(fā)現(xiàn) test() 方法里已經(jīng)沒了對 sum() 方法的調(diào)用迹辐,凡是原來代碼里出現(xiàn) sum() 方法調(diào)用的地方,出現(xiàn)的都是 sum() 方法體內(nèi)的字節(jié)碼了甚侣。

2. noinline

如果一個(gè)內(nèi)聯(lián)函數(shù)的參數(shù)里包含 lambda表達(dá)式明吩,也就是函數(shù)參數(shù),那么該形參也是 inline 的殷费,舉個(gè)例子:

inline fun test(inlined: () -> Unit) {...}

這里有個(gè)問題需要注意印荔,如果在內(nèi)聯(lián)函數(shù)的內(nèi)部,函數(shù)參數(shù)被其他非內(nèi)聯(lián)函數(shù)調(diào)用宗兼,就會(huì)報(bào)錯(cuò)躏鱼,如下所示:

//內(nèi)聯(lián)函數(shù)
inline fun test(inlined: () -> Unit) {
    //這里會(huì)報(bào)錯(cuò)
    otherNoinlineMethod(inlined)   
}

//非內(nèi)聯(lián)函數(shù)
fun otherNoinlineMethod(oninline: () -> Unit) {
    
}

要解決這個(gè)問題,必須為內(nèi)聯(lián)函數(shù)的參數(shù)加上 noinline 修飾殷绍,表示禁止內(nèi)聯(lián)染苛,保留原有函數(shù)的特性,所以 test() 方法正確的寫法應(yīng)該是:

inline fun test(noinline inlined: () -> Unit) {
    otherNoinlineMethod(inlined)
}

3. crossinline

首先來理解一個(gè)概念:非局部返回主到。我們來舉個(gè)栗子:

fun test() {
    innerFun {      
        //return  這樣寫會(huì)報(bào)錯(cuò)茶行,非局部返回,直接退出 test() 函數(shù)登钥。
        return@innerFun     //局部返回畔师,退出 innerFun() 函數(shù),這里必須明確退出哪個(gè)函數(shù)牧牢,寫成 return@test 則會(huì)退出 test() 函數(shù)
    }
    
    //以下代碼依舊會(huì)執(zhí)行
    println("test...")
}

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

非局部返回我的理解就是返回到頂層函數(shù)看锉,如上面代碼中所示,默認(rèn)情況下是不能直接 return 的塔鳍,但是內(nèi)聯(lián)函數(shù)確是可以的伯铣。所以改成下面這個(gè)樣子:

fun test() {
    innerFun {      
        return //非局部返回,直接退出 test() 函數(shù)轮纫。
    }
    
    //以下代碼不會(huì)執(zhí)行
    println("test...")
}

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

也就是說內(nèi)聯(lián)函數(shù)的函數(shù)參數(shù)在調(diào)用時(shí)腔寡,可以非局部返回,如上所示掌唾。那么 crossinline 修飾的 lambda 參數(shù)放前,可以禁止內(nèi)聯(lián)函數(shù)調(diào)用時(shí)非局部返回。

fun test() {
    innerFun {      
        return //這里這樣會(huì)報(bào)錯(cuò)糯彬,只能 return@innerFun
    }
    
    //以下代碼不會(huì)執(zhí)行
    println("test...")
}

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

4. 具體化的類型參數(shù) reified

這個(gè)特性我覺得特別牛逼凭语,有了它可以少些好多代碼。在 Java 中是不能直接使用泛型的類型的撩扒,但是在 Kotlin 中確可以似扔。舉個(gè)栗子:

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

使用時(shí)直接傳入泛型即可,代碼簡潔明了:

startActivity<MainActivity>()

5. 小結(jié)

網(wǎng)上很多學(xué)習(xí)教程對內(nèi)聯(lián)函數(shù)的講解都是千篇一律,說實(shí)話剛開始很難理解虫几。本文嘗試著用最簡單的例子,來講清楚什么是內(nèi)聯(lián)函數(shù)挽拔。在Java中我們一般會(huì)有很多工具類辆脸、工具方法,在Kotlin中則沒有了工具類一說螃诅,通常都是將工具方法設(shè)計(jì)成頂層的內(nèi)聯(lián)函數(shù)來使用啡氢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市术裸,隨后出現(xiàn)的幾起案子倘是,更是在濱河造成了極大的恐慌,老刑警劉巖袭艺,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搀崭,死亡現(xiàn)場離奇詭異,居然都是意外死亡猾编,警方通過查閱死者的電腦和手機(jī)瘤睹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來答倡,“玉大人轰传,你說我怎么就攤上這事”衿玻” “怎么了获茬?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長倔既。 經(jīng)常有香客問我恕曲,道長,這世上最難降的妖魔是什么叉存? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任码俩,我火速辦了婚禮,結(jié)果婚禮上歼捏,老公的妹妹穿的比我還像新娘稿存。我一直安慰自己,他們只是感情好瞳秽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布瓣履。 她就那樣靜靜地躺著,像睡著了一般练俐。 火紅的嫁衣襯著肌膚如雪袖迎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機(jī)與錄音燕锥,去河邊找鬼辜贵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛归形,可吹牛的內(nèi)容都是我干的托慨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼暇榴,長吁一口氣:“原來是場噩夢啊……” “哼厚棵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蔼紧,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤婆硬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后奸例,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彬犯,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年查吊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躏嚎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菩貌,死狀恐怖卢佣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情箭阶,我是刑警寧澤虚茶,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站仇参,受9級特大地震影響嘹叫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诈乒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一罩扇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧怕磨,春花似錦喂饥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至导饲,卻和暖如春捞高,著一層夾襖步出監(jiān)牢的瞬間氯材,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工硝岗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氢哮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓型檀,卻偏偏與公主長得像命浴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子贱除,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349