一、inline
inline翻譯成中文的意思就是內(nèi)聯(lián),在kotlin里面inline被用來修飾函數(shù),表明當(dāng)前函數(shù)在編譯時(shí)是以內(nèi)嵌的形式進(jìn)行編譯的,從而減少了一層函數(shù)調(diào)用棧:
inline fun fun1() {
Log.i("tag", "1")
}
//調(diào)用
fun mainFun() {
fun1()
}
//實(shí)際編譯的代碼
fun mainFun() {
Log.i("tag", "1")
}
這樣寫的一點(diǎn)好處就是調(diào)用棧會(huì)明顯變淺:

但是這個(gè)好處對(duì)應(yīng)用程序的優(yōu)化影響非常小,幾乎可以忽略不計(jì)。甚至可能會(huì)由于多處調(diào)用代碼重復(fù)編譯導(dǎo)致編譯字節(jié)碼膨脹從而造成包體積變大的問題,這就得不償失。所以inline關(guān)鍵字的正確使用場景并不是如上圖所示。
我們都知道kotlin允許函數(shù)可以作為另一個(gè)函數(shù)的入?yún)?duì)象進(jìn)行調(diào)用,在實(shí)際調(diào)用處入?yún)⒌暮瘮?shù)體會(huì)被創(chuàng)建為一個(gè)對(duì)象:
fun fun1(doSomething: () -> Unit) {
Log.i("tag", "1")
doSomething()
}
//調(diào)用
fun mainFun() {
fun1 {
Log.i("tag", "2")
}
}
//實(shí)際編譯的代碼
fun mainFun() {
val f = object: Function0<Unit> {
override fun invoke() {
Log.i("tag", "2")
}
}
fun1(f)
}
一般情況下上圖所示的調(diào)用邏輯并沒有什么問題,創(chuàng)建一個(gè)小對(duì)象并不會(huì)對(duì)性能造成什么影響,但是如果我們將fun1放入for循環(huán)中呢:
fun mainFun() {
for (i in 0..1000) {
fun1 {
Log.i("tag", "2")
}
}
}
那么在短時(shí)間內(nèi)就會(huì)在mainFun函數(shù)中循環(huán)創(chuàng)建1000個(gè)f對(duì)象,這樣應(yīng)用進(jìn)程的內(nèi)存會(huì)瞬間飆升并造成某些性能上的嚴(yán)重問題,這就類似于為什么不讓在onDraw函數(shù)中創(chuàng)建局部對(duì)象。而作為fun1函數(shù)的創(chuàng)建者,我們無法知道調(diào)用者會(huì)在什么場景以及時(shí)機(jī)去調(diào)用fun1函數(shù),一旦出現(xiàn)上述重復(fù)創(chuàng)建大量函數(shù)對(duì)象的場景那么就會(huì)有嚴(yán)重的性能問題,而且這也是kotlin高階函數(shù)的一個(gè)性能隱患。所以,基于這個(gè)問題kotlin提供了inline關(guān)鍵字來解決。
inline關(guān)鍵字可以將函數(shù)體內(nèi)部的代碼內(nèi)聯(lián)到調(diào)用處,甚至還可以將函數(shù)體內(nèi)部的內(nèi)部的代碼也內(nèi)聯(lián)過去,而這個(gè)內(nèi)部的內(nèi)部的指的就是函數(shù)內(nèi)部的函數(shù)類型的參數(shù):
inline fun fun1(doSomething: () -> Unit) {
Log.i("tag", "1")
doSomething()
}
//調(diào)用
fun mainFun() {
for (i in 0..1000) {
fun1 {
Log.i("tag", "2")
}
}
}
//實(shí)際編譯的代碼
fun mainFun() {
for (i in 0..1000) {
Log.i("tag", "1")
Log.i("tag", "2")
}
}
這樣就避免了函數(shù)類型的參數(shù)所造成的臨時(shí)函數(shù)對(duì)象的創(chuàng)建,我們就可以在界面高頻刷新、大量循環(huán)的場景下放心調(diào)用fun1函數(shù)了。
總的來說,inline關(guān)鍵字讓函數(shù)以內(nèi)聯(lián)的方式進(jìn)行編譯避免創(chuàng)建函數(shù)對(duì)象來處理kotlin高階函數(shù)的天然性能缺陷。同時(shí),之前的文章中提到的kotlin的泛型實(shí)化,也是利用了inline關(guān)鍵字可以內(nèi)嵌函數(shù)代碼的特性而衍生出來的全新功能。
二、noinline
顧名思義,noinline的意思就是不內(nèi)聯(lián),這個(gè)關(guān)鍵字只能作用于內(nèi)聯(lián)高階函數(shù)的某個(gè)函數(shù)類型的參數(shù)上,表明當(dāng)前的函數(shù)參數(shù)不參與高階函數(shù)的內(nèi)聯(lián):
inline fun fun1(doSomething1: () -> Unit, noinline doSomething2: () -> Unit) {
Log.i("tag", "1")
doSomething1()
doSomething2()
}
//調(diào)用
fun mainFun() {
fun1({ Log.i("tag", "2") }, { Log.i("tag", "3") })
}
//實(shí)際編譯的代碼
fun mainFun() {
Log.i("tag", "1")
Log.i("tag", "2")
({Log.i("tag", "3")}).invoke()
}
但是這個(gè)關(guān)鍵字有什么用呢?
在kotlin中高階函數(shù)的函數(shù)類型的參數(shù)我們可以直接當(dāng)做一個(gè)函數(shù)去調(diào)用,但是函數(shù)類型的參數(shù)終究還是一個(gè)對(duì)象,既然是一個(gè)對(duì)象那么我們就可以以對(duì)象的形式去使用,就比如說作為函數(shù)返回值進(jìn)行返回:
inline fun fun1(doSomething1: () -> Unit, doSomething2: () -> Unit): () -> Unit {
Log.i("tag", "1")
doSomething1()
doSomething2()
return doSomething2//這里編譯器會(huì)提示報(bào)錯(cuò),因?yàn)樽鳛閮?nèi)聯(lián)函數(shù)的函數(shù)類型參數(shù),它已經(jīng)不能作為對(duì)象去使用了。如果不加inline關(guān)鍵字這里就是正確的
}
我們知道內(nèi)聯(lián)函數(shù)的函數(shù)類型參數(shù)是不會(huì)再創(chuàng)建函數(shù)對(duì)象的,也就是說它只是一個(gè)函數(shù)體而不是一個(gè)函數(shù)對(duì)象,所以它無法被當(dāng)做一個(gè)對(duì)象那樣作為返回值進(jìn)行返回。
所以當(dāng)我們?cè)趦?nèi)聯(lián)函數(shù)里面真的需要將一個(gè)函數(shù)類型的參數(shù)作為對(duì)象去使用時(shí)就需要noinline關(guān)鍵字去修飾它:
inline fun fun1(doSomething1: () -> Unit, noinline doSomething2: () -> Unit): () -> Unit {
Log.i("tag", "1")
doSomething1()
doSomething2()
return doSomething2//編譯正常
}
三、crossinline
crossinline的含義字面上可以理解為對(duì)inline做局部加強(qiáng)內(nèi)聯(lián)
我們先來看一個(gè)場景:
inline fun fun1(doSomething1: () -> Unit){
Log.i("tag", "1")
doSomething1()
}
//調(diào)用
fun mainFun() {
fun1 {
Log.i("tag", "2")
return//按照一般的原則,這里的return結(jié)束的應(yīng)該是fun1函數(shù)體中的邏輯,就是說Log.i("tag", "3")會(huì)被執(zhí)行到。但是fun1作為內(nèi)聯(lián)函數(shù)會(huì)在編譯時(shí)被完全鋪平,這里return結(jié)束的就是最外面mainFun函數(shù)的邏輯,Log.i("tag", "3")就不會(huì)被執(zhí)行到。
}
Log.i("tag", "3")
}
//實(shí)際編譯的代碼
fun mainFun() {
Log.i("tag", "1")
Log.i("tag", "2")
return
Log.i("tag", "3")
}
按照一般的原則,這里的return結(jié)束的應(yīng)該是fun1函數(shù)體中的邏輯,就是說Log.i("tag", "3")會(huì)被執(zhí)行到。但是fun1作為內(nèi)聯(lián)函數(shù)會(huì)在編譯時(shí)被完全鋪平,這里return結(jié)束的就是最外面mainFun函數(shù)的邏輯,Log.i("tag", "3")就不會(huì)被執(zhí)行到。那我們?cè)诤瘮?shù)類型參數(shù)的lambda表達(dá)式中執(zhí)行的return到底結(jié)束的是當(dāng)前函數(shù)還是最外層的函數(shù)完全要看fun1到底是不是內(nèi)聯(lián)函數(shù),這就讓我們代碼敲得很難受。為此,kotlin提出了一個(gè)新規(guī)定:lambda表達(dá)式中不允許直接使用return,除非是內(nèi)聯(lián)函數(shù)的函數(shù)類型參數(shù)的lambda表達(dá)式,并且它結(jié)束的是最外層的函數(shù)邏輯。同時(shí)其他場景下的lambda表達(dá)式中可以通過return@label的形式來顯示指定return結(jié)束的代碼作用域:
fun fun1(doSomething1: () -> Unit){
Log.i("tag", "1")
doSomething1()
}
fun mainFun() {
fun1 {
Log.i("tag", "2")
//return//直接return在這里是不被允許的,因?yàn)閒un1不是內(nèi)聯(lián)函數(shù)
return@fun1//return@label的形式
}
Log.i("tag", "3")
}
再來看一種場景:
inline fun fun1(doSomething1: () -> Unit){
Log.i("tag", "1")
Runnable {
doSomething1()//這里會(huì)編譯報(bào)錯(cuò)
}
}
//調(diào)用
fun mainFun() {
fun1 {
Log.i("tag", "2")
return
}
Log.i("tag", "3")
}
我們?cè)趦?nèi)聯(lián)函數(shù)fun1中將函數(shù)類型參數(shù)doSomething1放到了子線程里面去執(zhí)行,這樣doSomething1和fun1就屬于間接調(diào)用的關(guān)系,那mainFun函數(shù)中的return結(jié)束的到底是Runnable子線程中的邏輯還是mainFun函數(shù)中的邏輯呢?所以kotlin是不允許直接這樣寫的,但是我確實(shí)有需求要這樣在內(nèi)聯(lián)函數(shù)中間接調(diào)用函數(shù)類型的參數(shù)怎么辦?kotlin為此又新增了一條規(guī)定:內(nèi)聯(lián)函數(shù)中不允許類似上述問題中對(duì)函數(shù)類型參數(shù)的間接調(diào)用,除非該函數(shù)類型參數(shù)被crossinline關(guān)鍵字修飾:(這就是局部加強(qiáng)內(nèi)聯(lián)的含義)
inline fun fun1(crossinline doSomething1: () -> Unit){
Log.i("tag", "1")
Runnable {
doSomething1()
}
}
//調(diào)用
fun mainFun() {
fun1 {
Log.i("tag", "2")
return//這里會(huì)編譯報(bào)錯(cuò)
}
Log.i("tag", "3")
}
如上圖所示,crossinline可以到達(dá)間接調(diào)用函數(shù)類型參數(shù)的目的,但是并沒有解決“mainFun函數(shù)中的return結(jié)束的到底是Runnable子線程中的邏輯還是mainFun函數(shù)中的邏輯呢?”這一嚴(yán)重問題,于是kotlin干脆在間接調(diào)用的情況下內(nèi)聯(lián)函數(shù)的函數(shù)類型參數(shù)的lambda表達(dá)式不允許直接使用return,即下面這兩個(gè)規(guī)定不可以共存:
1.lambda表達(dá)式中不允許直接使用return,除非是內(nèi)聯(lián)函數(shù)的函數(shù)類型參數(shù)的lambda表達(dá)式,并且它結(jié)束的是最外層的函數(shù)邏輯
2.內(nèi)聯(lián)函數(shù)中不允許類似上述問題中對(duì)函數(shù)類型參數(shù)的間接調(diào)用,除非該函數(shù)類型參數(shù)被crossinline關(guān)鍵字修飾
但是這里仍然可以用return@label的形式來顯示指定return結(jié)束的代碼作用域:
inline fun fun1(crossinline doSomething1: () -> Unit){
Log.i("tag", "1")
Runnable {
doSomething1()
}
}
//調(diào)用
fun mainFun() {
fun1 {
Log.i("tag", "2")
return@fun1//編譯正常
}
Log.i("tag", "3")
}
————————————————
版權(quán)聲明:本文為CSDN博主「我們間的空白格」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_37159335/article/details/123658961