Kotlin Lambda 與高階函式
Kotlin 是 函數式程式語言 (Functional Programming) 的擁護者。 這意味著函式是 一等公民 (First-class citizen):可以被存成變數、當作參數傳遞、也可以當作回傳值。
Lambda 表達式 (Lambda Expression)
Lambda 就是一個「沒有名字的函式」。
語法:{ 參數 -> 程式本體 }
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2)) // 3
隱式回傳 (Implicit Return)
Lambda 表達式最後一行的執行結果,會自動變成該 Lambda 的回傳值。不需要寫 return。
val calculate = { a: Int, b: Int ->
val result = a * b
result // 自動回傳 result,不用寫 return result
}
高階函式 (Higher-Order Functions)
接受函式當作參數,或是回傳一個函式的函式,就叫高階函式。 最常見的例子就是集合操作常用的 filter、map。
val numbers = listOf(1, 2, 3, 4, 5)
// 把 lambda 傳給 filter
val evens = numbers.filter { it % 2 == 0 }
唯一的參數 (it)
如果 Lambda 只有一個參數,可以省略參數宣告 x ->,直接用 it 代表那個參數。
// 完整寫法
numbers.map { x -> x * 2 }
// 簡寫 (推薦)
numbers.map { it * 2 }
方法參考 (Member References)
如果你的 Lambda 只是單純呼叫另一個函式,可以使用 :: 運算子來簡化。
fun isEven(x: Int) = x % 2 == 0
val numbers = listOf(1, 2, 3, 4)
// 傳統 Lambda
numbers.filter { isEven(it) }
// 方法參考 (更簡潔)
numbers.filter(::isEven)
未使用的參數 (Underscore for unused variables)
如果 Lambda 有多個參數,但你用不到其中幾個,可以使用底線 _ 來代替變數名稱,告訴編譯器(和閱讀程式碼的人)這個參數被忽略了。
map.forEach { _, value ->
println("$value") // 我們只在乎 value,key 用不到
}
解構宣告 (Destructuring Declarations)
如果 Lambda 的參數是 Pair、Map.Entry 或任何支援解構的資料類別,我們可以直接在參數列進行解構。
val map = mapOf("key1" to 1, "key2" to 2)
// 傳統寫法
map.forEach { entry ->
println("${entry.key}: ${entry.value}")
}
// 解構寫法 (推薦)
map.forEach { (key, value) ->
println("$key: $value")
}
尾隨 Lambda (Trailing Lambda)
如果函式的 最後一個參數 是函式,你可以把 Lambda 把移到括號外面。
// 定義 High-Order Function
fun logic(n: Int, operation: (Int) -> Int) {
println(operation(n))
}
// 呼叫方式 1
logic(5, { it * it })
// 呼叫方式 2 (推薦:尾隨寫法)
logic(5) {
it * it
}
你看,這是不是很像在寫一個語言本身的語法結構?Android 的 Jetpack Compose 就是大量使用這種寫法。
函式型別 (Function Type)
變數可以存函式,那變數的型別是什麼?格式為 (參數型別) -> 回傳型別。
val onClick: () -> Unit = { println("Clicked") }
val sum: (Int, Int) -> Int = { a, b -> a + b }
帶有接收者的 Lambda (Lambda with Receiver)
這是 Kotlin 最強大的特性之一,也是打造 DSL (Domain Specific Language) 的基石。
它的型別寫法是 A.() -> B。這表示這個 Lambda 是在「型別 A 的 context」中執行。
it(隱式參數):普通的 Lambda 使用it來代表傳入的參數。this(隱式接收者):帶有接收者的 Lambda 使用this來代表那個接收者物件 (Receiver)。而且this可以省略!
// 普通 Lambda: (StringBuilder) -> Unit
val buildStringOld = { sb: StringBuilder ->
sb.append("Hello")
sb.append(" World")
}
// 帶有接收者的 Lambda: "StringBuilder.() -> Unit"
val buildStringNew: StringBuilder.() -> Unit = {
// 這裡的 this 是 StringBuilder
this.append("Hello")
append(" World") // this 可以省略!看起來就像在寫 StringBuilder 內部的程式碼
}
fun main() {
val sb = StringBuilder()
buildStringNew(sb) // 呼叫方式 1
sb.buildStringNew() // 呼叫方式 2 (像擴充函式一樣呼叫)
}
上面的 StringBuilder.() -> Unit 它告訴 Kotlin,這個函式(Lambda)必須在 StringBuilder 的實例(Instance)上執行。() 代表這個函式不需要傳入參數;-> Unit 代表這個函式執行後不回傳任何值。
這就是為什麼 apply, run, with 這些 Scope Functions 可以在 {} 裡面直接呼叫物件的方法,因為它們的參數就是 Lambda with Receiver。
Closure (閉包)
Kotlin 的 Lambda 可以存取並「修改」外部的變數(Java 的 Lambda 只能存取 final 變數)。
var sum = 0
numbers.filter { it > 0 }.forEach {
sum += it // 直接修改外部變數
}
println(sum)
從 Lambda 返回 (Qualified Return)
Lambda 預設不能使用裸 return (Bare return),因為這會讓編譯器混淆:到底是要從 Lambda 返回,還是從外層的函式返回?
如果你想要從 Lambda 中提早結束,必須使用標籤限制的 return (Qualified Return),語法為 return@label。
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
println("Start")
numbers.forEach {
if (it == 3) {
// return // 錯誤!這會直接跳出 main() 函式 (如果 forEach 是 inline 的話)
return@forEach // 正確:只跳出這一次的 Lambda 執行 (相當於 loop 的 continue)
}
println(it)
}
println("End")
}
在 forEach 中使用 return@forEach 的行為類似於 continue (跳過當前元素,繼續下一個)。如果你想要類似 break 的效果 (停止整個迴圈),通常建議改用標準的 for 迴圈。