如何使用 inline 函式

如何使用 inline 函式

透過 inline 函式可以優化效能,所有的 Collection API 絕大部分都是 inline 函式,避免頻繁呼叫這些函式的時候,造成不必要的浪費。

說明

講到 inline 首先我們會先討論一下 Lambda function,什麼是 Lambda function?

我們通常在 Java 會看到這樣的寫法。

/* Java */
public interface OnClickListener{ 
	void onClick(View v);
}

而在某個類別會透過上面的介面來完成某些事件處理。

/* Java */
public class Button {
	public void setOnClickListener(OnClickListener l) { ... }
}

所以當我們實作的時候就會長這樣。

/* Java */
button.setOnClickListener(new OnClickListener() {
	@Override
    public void onClick(View v) {
  		...
  	}
}

那我們在 Kotlin 就可以寫成這樣。

button.setOnClickListener { view -> ... }

這種就稱之為 Lambda,但是要注意的一點是這個介面只能有一個實作方式,我們稱之為 Single Abstract Method(SAM),SAM 建構方法是編譯器生成的一種函式,讓你能夠快速的實作介面的一種顯性轉換,透過這種快速轉換去掉冗長的語法贅詞,可以讓我們專注在處理的事情上。

除了快速轉換語法以外,SAM 還可以幫忙把結果傳遞出去,舉個例子來說。

val runnable = Runnable{ println("hello") }

這樣一來我們就可以把 runnable 這個變數傳遞到所需的地方。
回到我們前面所討論的,什麼是 inline 函式?它用來解決什麼問題呢?
假設你有一個函式像下面這樣。

fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit): Int {
    val r = a + b
    lambda.invoke(r)
    return r
}

然後你就去 call 它。

fun main(args: Array<String>) {
    sum(1, 2) { println("Result is: $it") }
}

所以當你透過 Android Studio 的 decompile 工具去看的時候,你會發現 Java 程式長這樣。

//decompile
public static final void main(@NotNull String[] args) {
   //...
   sum(1, 2, (Function1)null.INSTANCE);
}

也就是說,每一個 Lambda 在處理的時候,都會生成一個 instance,所以當我們如果跑迴圈來處理這個函式的時候,就會不斷地產生新的 instance,那這樣該怎麼優化呢?

Kotlin 提供了 inline 函式就是為了解決這個問題,透過 inline 關鍵字,我們同樣的程式碼反組譯完就會變成下面這樣。

//decompile
public static final void main(@NotNull String[] args) {
	//...
	byte a$iv = 1;
	int b$iv = 2;
	int r$iv = a$iv + b$iv;
	String var9 = "Result is: " + r$iv;
	System.out.println(var9);
}

也就是說少掉了 function call 的部分,直接把 function 的內容複製貼上到你 call 的那個地方,所以,就會節省掉 function call 的額外支出,這樣一來,如果你的 function 是很頻繁的呼叫的話,就可以有效地節省這一塊效能,而且我們可以很容易發現, Collection API 幾乎全部都宣告成為 inline 函式,就是為了提高效能。