kotlin的內聯函數之inline、noinline、crossinline、reified

首先看下方法的調用流程

調用一個方法其實就是一個方法壓棧和出棧的過程,調用方法時將棧幀壓入方法棧,然後執行方法體,方法結束時將棧幀出棧,這個壓棧和出棧的過程是一個耗費資源的過程,這個過程中傳遞形參也會耗費資源。

爲什麼要使用內聯函數inline

我們在寫代碼的時候難免會遇到這種情況,就是很多處的代碼是一樣的,於是乎我們就會抽取出一個公共方法來進行調用,這樣看起來就會很簡潔;但是也出現了一個問題,就是這個方法會被頻繁調用,就會很耗費資源
舉個栗子:

fun <T> method(lock: Lock, body: () -> T): T {
        lock.lock()
        try {
            return body()
        } finally {
            lock.unlock()
        }
    }

這裏的method方法在調用的時候是不會把形參傳遞給其他方法的,調用一下:

 method(lock, {"我是body的方法體"})//lock是一個Lock對象

對於編譯器來說,調用method方法就要將參數l和lambda表達式{“我是body的方法體”}進行傳遞,就要將method方法進行壓棧出棧處理,這個過程就會耗費資源。如果是很多地方調用,就會執行很多次,這樣就非常消耗資源了.
於是乎就引進了inline

inline

被inline標記的函數就是內聯函數,其原理就是:在編譯時期,把調用這個函數的地方用這個函數的方法體進行替換
舉個栗子:
我們調用上面的method方法

 method(lock, {"我是body方法體"})//lock是一個Lock對象

其實上面調用的方法,在編譯時期就會把下面的內容替換到調用該方法的地方,這樣就會減少方法壓棧,出棧,進而減少資源消耗;

        lock.lock()
        try {
            return "我是body方法體"
        } finally {
            lock.unlock()
        }

也就是說inline關鍵字實際上增加了代碼量,但是提升了性能,而且增加的代碼量是在編譯期執行的,對程序可讀性不會造成影響,可以說是非常的nice.

noinline

雖然內聯非常好用,但是會出現這麼一個問題,就是內聯函數的參數(ps:參數是函數,比如上面的body函數)如果在內聯函數的方法體內被其他非內聯函數調用,就會報錯.
舉個栗子:

inline fun <T> mehtod(lock: Lock, body: () -> T): T {
            lock.lock()
            try {
                otherMehtod(body)//會報錯
                return body()
            } finally {
                lock.unlock()
            }
    }

    fun <T> otherMehtod(body: ()-> T){

    }

原因:因爲method是內聯函數,所以它的形參也是inline的,所以body就是inline的,但是在編譯時期,body已經不是一個函數對象,而是一個具體的值,然而otherMehtod卻要接收一個body的函數對象,所以就編譯不通過了
解決方法:當然就是加noinline了,它的作用就已經非常明顯了.就是讓內聯函數的形參函數不是內聯的,保留原有的函數特徵.
具體操作:

fun main(args: Array<String>) {
    val lock = ReentrantLock()
    mehtod(lock,{"body方法體"})
}

inline fun <T> mehtod(lock: Lock, noinline body: () -> T): T {
    lock.lock()
    try {
        otherMehtod(body)
        return body()
    } finally {
        lock.unlock()
    }
}

fun <T> otherMehtod(body: ()-> T){

}

這樣編譯時期這個body函數就不會被內聯了
反編譯看下

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      ReentrantLock lock = new ReentrantLock();
      //這裏是生成了一個函數對象
      Function0 body$iv = (Function0)null.INSTANCE;
      ((Lock)lock).lock();

      try {
         otherMehtod(body$iv);
         Object var3 = body$iv.invoke();
      } finally {
         ((Lock)lock).unlock();
      }

   }

   public static final Object mehtod(@NotNull Lock lock, @NotNull Function0 body) {
      Intrinsics.checkParameterIsNotNull(lock, "lock");
      Intrinsics.checkParameterIsNotNull(body, "body");
      lock.lock();

      Object var3;
      try {
         otherMehtod(body);
         var3 = body.invoke();
      } finally {
         InlineMarker.finallyStart(1);
         lock.unlock();
         InlineMarker.finallyEnd(1);
      }

      return var3;
   }

   public static final void otherMehtod(@NotNull Function0 body) {
      Intrinsics.checkParameterIsNotNull(body, "body");
   }

crossinline

什麼是crossinline呢,crossinline 的作用是讓被標記的lambda表達式不允許非局部返回。
怎麼理解呢?
首先我們來看下非局部返回
我們都知道,kotlin中,如果一個函數中,存在一個lambda表達式,在該lambda中不支持直接通過return退出該函數的,只能通過return@XXXinterface這種方式
舉個栗子:

fun outterFun() {
    innerFun {
        //return  //錯誤,不支持直接return
        //只支持通過標籤,返回innerFun
        return@innerFun 1
    }

    //如果是匿名或者具名函數,則支持
    var f = fun(){
        return
    }
}

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

但是如果這個函數是內聯的卻是可以的

fun outterFun() {
    innerFun {
        return  //支持直接返回outterFun       
    }
}

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

這種直接從lambda中return掉函數的方法就是非局部返回
crossinline就是爲了讓其不能直接return
舉個栗子

fun outterFun() {
    innerFun {
        return  //這樣就報錯了
    }
}

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

這裏的a函數就是被crossinline修飾了,如果在lambda中直接return就無法編譯通過;
官方給出的解釋:
一些內聯函數可能調用傳給它們的不是直接來自函數體、而是來自另一個執行上下文的 lambda 表達式參數,
例如來自局部對象或嵌套函數。在這種情況下,該 lambda 表達式中也不允許非局部控制流。爲了標識這種情況
,該 lambda 表達式參數需要用 crossinline 修飾符標記。

說白了,我們如果直接在lambda參數中結束當前函數,而不給lambda提供一個返回值,這種情況是不被允許的
當然這個使用的機會並不多,但是有的時候還是會用到.
舉個栗子:
這裏寫代碼片:

fun main(args: Array<String>) {
    //正常
    method{
        1
    }
    //return報錯
    method{
        return 
    }
}
interface TestInter{
    fun test(a:Int):Int
}
inline fun method(crossinline t: (Int) -> Int): TestInter = object : TestInter {
    override fun test(a: Int): Int = t.invoke(a)
}

這裏如果不通過crossinline禁止lambda表達式t直接執行的return操作,那麼t直接return後,返回值是Unit,這並不符合fun test(a: Int): Int 需要Int返回值的要求,就返回了一個Unit,這樣是不符合需求的.

reified

什麼是reified,字面意思:具體化,其實就是具體化泛型;
我們都知道在java中如果是泛型,是不能直接使用泛型的類型的,但是kotlin卻是可以的,這點和java就有了顯著的區別.
通常java中解決的方案就是通過函數來傳遞類
但是kotlin就老牛逼了,直接就可以用了,主要還是有內聯函數inline這個好東西,才使得kotlin能夠直接通過泛型就能拿到泛型的類型.
舉個栗子:

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

通過kotlin的拓展寫個啓動activity的方法,只需要傳入該activity的泛型即可
startActivity()

是不是很簡單,很爽.
再來看看一個需求,有的時候我們需要創建一個Fragment的實例,並且要傳遞參數,如果是之前你可能會在每個Fragment裏面這樣寫:

 fun newInstance(param: Int): ActyFragment {
            val fragment = ActyFragment()
            val args = Bundle()
            args.putInt(PARAMS, param)
            fragment.arguments = args
            return fragment
        }

但是這樣是不是很low,只要需要的Fragment都要寫個這個,有強迫症的人是受不了的
現在通過reified來優化

inline fun <reified F : Fragment> Context.newFragment(vararg args: Pair<String, String>): F {
    val bundle = Bundle()
    args.let {
        for (arg in args) {
            bundle.putString(arg.first, arg.second)
        }
    }
    return Fragment.instantiate(this, F::class.java.name, bundle) as F
}

這樣就不要每個Fragment都寫個方法了,可以說非常的nice了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章