iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 27
0
Software Development

新手也能懂的 Kotlin Collection 賞玩門道系列 第 27

第二十七天:深入 Collection 核心 - Lambda

在前面一系列的 Collection 操作章節裡,你會發現只要能客製化操作行為的 method,都是透過讓你傳入一個 Lambda 的方式達成。到底什麼是 Lambda?Collection 核心又是如何利用 Lambda?我們將在這章深入探討。

Lambda 簡介

一般我們在定義函式的時候,只要用 fun 關鍵字,加上函式名稱、以 () 宣告參數、以 {} 括住函式的所有動作即可。

fun hello() {
    // ...
}

Kotlin 支援一種特別的函式稱為 Lambda,這種函式不需要定義名字,又常被稱為匿名函數。Lambda 是一套數學演算邏輯,由數學家 Alonzo Church 於 20 世紀 30 年代發明,可以用希臘字元 λ 表示。當定義匿名函數時,就使用了 Lambda 演算法。

{
    val name = "Shengyou"
    println("Hello, $name")
}()

我們可以看到上面這段程式碼沒有 fun 關鍵字、沒有函式的名字,只用 {} 定義函式行為並以 () 直接執行。

當然,我們也可以把 Lambda 儲存在變數裡,等到需要的再呼叫。

val myGreetingFun: () -> Unit = {
    val name = "Shengyou"
    println("Hello, $name")
}

myGreetingFun()

跟上例不同,把 Lambda 宣告在變數時不需要加上 ()。而呼叫時則跟函式相同,以變數的名稱加上 () 即可執行。

注意一下變數的型別,不是常見的標準型別,而是 () -> Unit。這代表變數的型別是一個函式類別(Function Type),() 代表函式本身、-> 代表函式執行、Unit 代表函式回傳的型別。

把 Lambda 當參數傳入函式

Lambda 可以存在變數裡,也可以當成參數傳入函式裡。在宣告參數型別時,跟宣告變數型別一樣,採用 () -> Type 的格式標明。

fun hello(name: String, greeting: (String) -> String): String {
    return greeting(name)
}

呼叫這個函式時,要把所需的 name 字串及 greeting 函式傳入。若 Lambda 是函式的最後一個參數,則可以將函式本體放在 () 的外面;若 Lambda 是函式的唯一一個參數,甚至可以直接省略 ()

// 呼叫函式
hello("Shengyou", {
    // ...
})

// 將最後一個參數 Lambda 放到 `()` 外面
hello("Shengyou") {
    // ...
}

it 關鍵字

若 Lambda 本身只接受一個參數,則在 Lambda 內部可以用 it 來表示該參數。假如 Lambda 動作單純,可以用 it 關鍵字讓程式碼更精簡。不過若是 Lambda 有多個參數、或是遇到巢狀 Lambda 時,建議還是顯式的標示參數名稱以提升程式可讀性。

hello("Shengyou") {
    "Hello, $it" // it 就代表 Lambda 拿到的 name 參數,在此例裡就是 Shengyou
}

隱式返回

在上面的範例裡,你會發現雖然 Lambda 是函式的一種,也有定義回傳型別,但卻不用 return 關鍵字?沒錯!這是因為 Lambda 會自動回傳本體最後一行語句的結果。

inline 機制

雖然 Lambda 很靈活好用,但因為在 JVM 上它是以物件實例的型式存在,換句話說,所有和 Lambda 互動的變數都會產生記憶體開銷,長期下來會有效能問題。

為了解決這個問題 Kotlin 提供 inline 機制,只要在 fun 關鍵字前加上 inline,編譯器就會將 Lambda 本體直接複製貼到那裡,如此一來就能避免 Lambda 函數的傳遞,也不需要建立新的物件實例了。

一探 forEach() 原始碼

有了以上的語法基礎,再看 Collection 的原始碼就能秒懂其運作原理。這邊以 List 的 forEach() 為例:

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

我們可以看到 forEach 這個函式只接受一個名為 action 的 Lambda 參數並回傳 Unit,而這個 action 接受一個泛型參數並回傳 Unit,在函式本體裡,用 for 將 Collection 逐一取出成 element,每一個 element 都傳給 action 做處理。而 forEach 有使用到 Lambda,因此有加上 inline 宣告。

看懂愈多 Collection 的原始碼,是不是覺得 Collection 的實作非常精妙呢?

參考資料

  • Kotlin 權威 2.0:Android 專家養成術第 5 章 - 匿名函數與函數類型

上一篇
第二十六天:深入 Collection 核心 - 泛型
下一篇
第二十八天:深入 Collection 核心 - Extension
系列文
新手也能懂的 Kotlin Collection 賞玩門道31

尚未有邦友留言

立即登入留言