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ù)來使用啡氢。