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

Kotlin 中新增了「內(nèi)聯(lián)函數(shù)」,內(nèi)聯(lián)函數(shù)起初是在 C++ 里面的。

那在 Kotlin 中加入內(nèi)聯(lián)函數(shù),是有什么作用呢恩掷?

以下內(nèi)容分為以下幾部分:

  1. 什么是 inline 內(nèi)聯(lián)函數(shù)
  2. inline 內(nèi)聯(lián)函數(shù)的作用和使用
    • 2.1 不應(yīng)該使用 inline 的情況
    • 2.2 應(yīng)該使用 inline 的情況
    • 2.3 inline 提高效率的原因
  3. 內(nèi)聯(lián)函數(shù)的一些其他用處;
    • 3.1 支持 return 退出函數(shù)
    • 3.2 禁止內(nèi)聯(lián):noinline
  4. 小結(jié)
  5. 參考鏈接

1. 什么是 inline 內(nèi)聯(lián)函數(shù)呢

簡單來說:
當(dāng)一個函數(shù)被內(nèi)聯(lián) inline 標(biāo)注后供嚎,在調(diào)用它的地方黄娘,會把這個函數(shù)方法體中的所以代碼移動到調(diào)用的地方,而不是通過方法間壓棧進(jìn)棧的方式克滴。

代碼示例:
1.1 使用 inline 的代碼

// 在 main() 中調(diào)用 makeTest()
fun main() {
    Log.i("zc_test", "main() start")
    makeTest()
    Log.i("zc_test", "main() end")
}
// 內(nèi)聯(lián)函數(shù) makeTest()
private inline fun makeTest() {
    Log.i("zc_test", "makeTest")
}

1.2 使用 inline 編譯成 java 的代碼

public final void main() {
    Log.i("zc_test", "main() start");
    int $i$f$makeTest = false;
    Log.i("zc_test", "makeTest");
    Log.i("zc_test", "main() end");
}

1.3 當(dāng) makeTest() 不在被 inline 修飾時, 被編輯成 java 的代碼為:

public final void main() {
    Log.i("zc_test", "main() start");
    this.makeTest();
    Log.i("zc_test", "main() end");
}

可以看到逼争,當(dāng) makeTest()inline 修飾時, 在 main() 中原來調(diào)用 makeTest() 的地方被替換成了 makeTest() 里面的代碼劝赔。

換句話說:在編譯時期誓焦,把調(diào)用這個函數(shù)的地方用這個函數(shù)的方法體進(jìn)行替換。

這就是 inline 的本質(zhì)。

至于 Kotlin 內(nèi)聯(lián)函數(shù)有什么作用呢杂伟?

2. Kotlin 內(nèi)聯(lián)函數(shù)的作用和使用
由上面可以知道移层, inline 的本質(zhì):在編譯時期,把調(diào)用這個函數(shù)的地方用這個函數(shù)的方法體進(jìn)行替換赫粥。

那么我們什么時候應(yīng)該使用 inline 什么時候不應(yīng)該使用呢观话?

2.1 不應(yīng)該使用 inline 的情況
當(dāng)使用 inline 標(biāo)注時,如果是下面這樣越平,無參數(shù)的函數(shù)時:

//makeTest() 沒有任何的參數(shù)
private inline fun makeTest() {
     Log.i("zc_test", "makeTest")
}
//或者帶有基本變量參數(shù)的函數(shù)频蛔,編譯器也會報錯。
private inline fun makeTest2(test: String) {
     Log.i("zc_test", "makeTest")
}

這個時候 AndroidStudio 編譯器會在 inline 位置有黃色警告秦叛,
Expected performance impact of inlining '...' can be insignificant. Inlining works best for functions with lambda parameters,
翻譯過來就是晦溪,在這個位置使用 inline 并不會有很大的提高,inline 適合在包含 lambda 參數(shù)的函數(shù)上挣跋。

也就是說 inline 在一般的方法是標(biāo)注三圆,是不會起到很大作用的,inline 能帶來的性能提升避咆,往往是在參數(shù)是 lambda 的函數(shù)上嫌术。


在一篇文章上看到這樣一段話:
眾所周知,JVM 內(nèi)部已經(jīng)實現(xiàn)了內(nèi)聯(lián)優(yōu)化牌借,它會在任何可以通過內(nèi)聯(lián)來提升性能的地方將函數(shù)調(diào)用內(nèi)聯(lián)化,并且相對于手動將普通函數(shù)定義為內(nèi)聯(lián)割按,通過 JVM 內(nèi)聯(lián)優(yōu)化所生成的字節(jié)碼膨报,每個函數(shù)的實現(xiàn)只會出現(xiàn)一次,這樣在保證減少運行時開銷的同時适荣,也沒有增加字節(jié)碼的尺寸现柠;
鏈接:http://www.reibang.com/p/678a49054238
來源:簡書

這段話也在間接證明了編譯器給的警告,inline 不適合在無參數(shù)的函數(shù)中弛矛, 適合在包含 lambda 參數(shù)的函數(shù)上够吩。


2.2 應(yīng)該使用 inline 的地方: 帶有 lambda 參數(shù)的函數(shù)

當(dāng)我們寫一個會被經(jīng)常調(diào)用的帶 lambda參數(shù)的函數(shù)時, 可使用該方式丈氓。

例如代碼:

// body 是本身一個函數(shù)
fun foo(body:() -> Unit) {
    println("foo() hahaha")
    ordinaryFunction(body)
}

inline fun ordinaryFunction(block: () -> Unit) {
    println("hahha")
    block.invoke()
    println("hahha233333")
}

在上述代碼中周循,我們把 foo() 的函數(shù)參數(shù) body 作為一個參數(shù)傳遞給 ordinaryFunction() ,

這是我們可以通過在 ordinaryFunction() 上面標(biāo)注 inline 從而使得方法的調(diào)用棧少一層,使得代碼變?yōu)椋?/p>

fun foo(body:() -> Unit) {
    println("hahha")
    block.invoke()
    println("hahha233333")
}

2.3 inline 的使用規(guī)則
那么什么時候使用万俗,什么時候不使用 inline 呢湾笛?
根據(jù)上面,我們大致可分為兩種:

  1. 不帶參數(shù)闰歪,或是帶有普通參數(shù)的函數(shù)嚎研,不建議使用 inline
  2. 帶有 lambda 函數(shù)參數(shù)的函數(shù),建議使用 inline

2.3 inline 提高效率的原因

為什么要使用 inline 呢库倘?必然是因為使用 inline 會帶來效率的提升临扮。
我們比較一下使用了 inline 和不使用 inline 編譯成 java 代碼的差異

當(dāng)然上述 ordinaryFunction() 也可以不使用 inline 標(biāo)注论矾,我們看一下編譯成 java 的代碼樣式, 「對比」添加了 inline 的標(biāo)注的 java 代碼,我們發(fā)現(xiàn)杆勇,當(dāng)不添加 inline 時贪壳,代碼中,多出了一個類:

final class TestInline$main$1$1 extends Lambda implements Function0 {
   public static final TestInline$main$1$1 INSTANCE = new TestInline$main$1$1();
   // $FF: synthetic method
   // $FF: bridge method
   public Object invoke() {
      this.invoke();
      return Unit.INSTANCE;
   }

   public final void invoke() {
   }

   TestInline$main$1$1() {
      super(0);
   }
}

它便是在編譯過程中靶橱,因為 lambda 參數(shù) 多出來的類寥袭,無疑中會增加內(nèi)存的分配。

所以我們就知道了关霸,在 kotlin 中传黄,因為出現(xiàn)了大量的 高階函數(shù) -- 「高階函數(shù)是將函數(shù)用作參數(shù)或返回值的函數(shù)」,使得越來越多的地方出現(xiàn) 函數(shù)參數(shù) 不斷傳遞的現(xiàn)象队寇,每一個函數(shù)參數(shù)都會被編譯成一個對象膘掰, 使得內(nèi)存分配(對于函數(shù)對象和類)和虛擬調(diào)用會增加運行時間開銷。所以才會出現(xiàn) inline 內(nèi)聯(lián)函數(shù)佳遣∈堵瘢可以通過 inline 的標(biāo)注,把原本需要生成一個類的開銷節(jié)省了零渐, 同時也少了一層方法棧的調(diào)用窒舟。

3. inline 的其他作用

除了上述的功能點外,還有一些值得注意的小地方诵盼。

3.1 支持 return 退出函數(shù)
在編碼中惠豺,我們通常習(xí)慣使用 return 返回退出這個函數(shù),但是 lambda 表達(dá)式不能使包含它的函數(shù)返回风宁。

例如代碼:

fun foo(body:()->Unit) {
    ordinaryFunction {
        println("zc_testlabama 表達(dá)式退出")
        return
    }
    println("zc_test --->foo() end")
} 
fun ordinaryFunction(block: () -> Unit) {
    println("hahha")
    block.invoke()
    println("hahha233333")
}

如果在 ordinaryFunction 這個方法沒有 inline 的標(biāo)注洁墙,編譯器會在 return 的位置出錯,return is not allowed here.

解決上述錯誤的方式戒财,可以為 return 添加標(biāo)簽热监,例如 return@ordinaryFunction, 但是這樣的話,方法執(zhí)行只會退出 lambda 表達(dá)式饮寞,后面的代碼 println("zc_test --->foo() end") 還是會走到的孝扛。

當(dāng)我們添加上 inline 時刃唐,正確的代碼如下:

fun foo(body:()->Unit) {
    ordinaryFunction {
        // 因為標(biāo)識為 inline 的函數(shù)會被插入到調(diào)用出宠叼,此時 return 肯定是 return 到該整個方法
        println("zc_testlabama 表達(dá)式退出")
        return
    }
    println("zc_test --->foo() end")
}
// 如果不使用 inline褐缠, 上面代碼會被報錯椿胯。因為「不允許這么做」
inline fun ordinaryFunction(block: () -> Unit) {
    println("hahha")
    block.invoke()
    println("hahha233333")
}

當(dāng)我們添加了 inline 標(biāo)志后封断,在 ordinaryFunction{}return 時就會退出整個 foo() 函數(shù)涣狗,因此結(jié)尾的 println("zc_test --->foo() end") 是不會被調(diào)用的碑诉。

inline 可以讓函數(shù)參數(shù)里面的 return 生效

kotlin 官方注釋:breakcontinue 在內(nèi)聯(lián)的 lambda 表達(dá)式中還不可用携取,但我們也計劃支持它們。

3.2 禁止內(nèi)聯(lián):noinline

為什么會有 noinline呢柠贤?為什么需要這種方式呢香浩?

官網(wǎng)中這么寫著:如果希望只內(nèi)聯(lián)一部分傳給內(nèi)聯(lián)函數(shù)的 lambda 表達(dá)式參數(shù),那么可以用 noinline 修飾符標(biāo)記不希望內(nèi)聯(lián)的函數(shù)參數(shù), 代碼如:

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

什么時候我們會需要 noinline 呢臼勉?
例如代碼:

inline fun foo(testName:String, body:()->Unit) {
    // 這里會報錯邻吭。。宴霸。
    ordinaryFunction(body)
    println("zc_test --->foo() end")
} 
fun ordinaryFunction(block: () -> Unit) {
    println("hahha")
    block.invoke()
    println("hahha233333")
}

如果 ordinaryFunction() 不使用 inline 標(biāo)注囱晴,是一般的函數(shù),這里是不允許把內(nèi)聯(lián)函數(shù) foo() 的函數(shù)參數(shù) body 傳遞給 ordinaryFunction()瓢谢。

即:內(nèi)聯(lián)函數(shù)的「函數(shù)參數(shù)」 不允許作為參數(shù)傳遞給非內(nèi)聯(lián)的函數(shù)畸写,
如果我們想要實現(xiàn)上述的調(diào)用,便可以使用 noinline 標(biāo)注內(nèi)聯(lián)函數(shù) foo()body 參數(shù)

inline fun foo(testName:String, noinline body:()->Unit) {
    ...
}

上述代碼便可以正常運行了氓扛。

4. 小結(jié):

上面對 inline 做了一些簡單的介紹枯芬。大部分都是實踐中產(chǎn)生的結(jié)論。
使用了很多代碼采郎,這是不可避免的千所,只有使用多了,才會比較熟悉這些 kotlin 中的屬性蒜埋。

當(dāng)然也很局限淫痰,個人水平有限,有如錯誤整份,還請指出黑界。

5. 參考鏈接:

簡書:http://www.reibang.com/p/678a49054238

官網(wǎng):https://www.kotlincn.net/docs/reference/inline-functions.html

文章來自: kotlin inline 初步解析

2019.10.24 by chendroid

PS: 1024 程序員日,愿每個認(rèn)真開發(fā)的工程師皂林,都能夠生活開心,工作順利蚯撩,身體健康础倍,錢多,活少胎挎,身體好沟启!

相關(guān)文章:kotlin 作用域函數(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市犹菇,隨后出現(xiàn)的幾起案子德迹,更是在濱河造成了極大的恐慌,老刑警劉巖揭芍,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胳搞,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機肌毅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門筷转,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悬而,你說我怎么就攤上這事呜舒。” “怎么了笨奠?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵袭蝗,是天一觀的道長。 經(jīng)常有香客問我般婆,道長到腥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任腺兴,我火速辦了婚禮左电,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘页响。我一直安慰自己篓足,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布闰蚕。 她就那樣靜靜地躺著栈拖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪没陡。 梳的紋絲不亂的頭發(fā)上涩哟,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音盼玄,去河邊找鬼贴彼。 笑死,一個胖子當(dāng)著我的面吹牛埃儿,可吹牛的內(nèi)容都是我干的器仗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼童番,長吁一口氣:“原來是場噩夢啊……” “哼精钮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起剃斧,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤轨香,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后幼东,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體臂容,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡科雳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了策橘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炸渡。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖丽已,靈堂內(nèi)的尸體忽然破棺而出蚌堵,到底是詐尸還是另有隱情,我是刑警寧澤沛婴,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布吼畏,位于F島的核電站,受9級特大地震影響嘁灯,放射性物質(zhì)發(fā)生泄漏泻蚊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一丑婿、第九天 我趴在偏房一處隱蔽的房頂上張望性雄。 院中可真熱鬧,春花似錦羹奉、人聲如沸秒旋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迁筛。三九已至,卻和暖如春耕挨,著一層夾襖步出監(jiān)牢的瞬間细卧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工筒占, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贪庙,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓翰苫,卻偏偏與公主長得像插勤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子革骨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容