Kotlin
中新增了「內(nèi)聯(lián)函數(shù)」,內(nèi)聯(lián)函數(shù)起初是在 C++
里面的。
那在 Kotlin
中加入內(nèi)聯(lián)函數(shù),是有什么作用呢恩掷?
以下內(nèi)容分為以下幾部分:
- 什么是
inline
內(nèi)聯(lián)函數(shù) -
inline
內(nèi)聯(lián)函數(shù)的作用和使用- 2.1 不應(yīng)該使用
inline
的情況 - 2.2 應(yīng)該使用
inline
的情況 - 2.3
inline
提高效率的原因
- 2.1 不應(yīng)該使用
- 內(nèi)聯(lián)函數(shù)的一些其他用處;
- 3.1 支持
return
退出函數(shù) - 3.2 禁止內(nèi)聯(lián):
noinline
- 3.1 支持
- 小結(jié)
- 參考鏈接
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ù)上面,我們大致可分為兩種:
- 不帶參數(shù)闰歪,或是帶有普通參數(shù)的函數(shù)嚎研,不建議使用
inline
- 帶有
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
官方注釋:break
和continue
在內(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ù)