首先看下方法的調用流程
調用一個方法其實就是一個方法壓棧和出棧的過程,調用方法時將棧幀壓入方法棧,然後執行方法體,方法結束時將棧幀出棧,這個壓棧和出棧的過程是一個耗費資源的過程,這個過程中傳遞形參也會耗費資源。
爲什麼要使用內聯函數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了.