初探 Kotlin Lambda 表達式
最近正在學習 Kotlin 的 Lambda 表達式,對於以前習慣使用 Java 的我來說有點陌生。雖然,Java 8 也導入了 Lambda 表達式的支援,寫起來比起過去直接實作匿名類型 (Anonymous Class) 更為簡潔。
看了 Kotlin 的 Lambda 表達式相關的文件後,發現 Kotlin 對於 Lambda 表達式的支援更為強大,並且在語言層級就可以將 Lambda Function 視為變數傳遞。在 Kotlin 的 Standard Library 之中許多 API 就有用到 Lambda Function,例如上次研究過的 T.run
、 T.let
、 T.with
⋯ 等等。甚至一些 String 或 Collections 使用的 API 也是 Lambda Function。
透過這次學習,大致了解了 Lambda 表達式長什麼樣子、要如何使用,首先從 Lambda 表達式本身開始介紹。
Lambda 表達式
Kotlin 裡面,大括號表示的範圍都可以視作一個 Function。例如一般 JVM 程式的進入點 main(argv Array<String>)
是一個 Function。在某一個 Function 裡頭再執行另一個 Function, Lambda 表達式常用來表示這種 Function 內的其他內部 Function。
常常見到的程式碼大概會是:
在其他語言之中,Lambda 表達式又稱作匿名函數 (Anonymous Function),我自己的理解認為 Lambda 表達式是一個沒有名字的 Function。根據前面的理解,在 Kotlin 之中可以將 Lambda 表達式寫成以下的格式:
接下來要來介紹 Kotlin 如何傳遞 Function,並且介紹如何定義的傳遞的 Function。
Higher-Order Functions
上面說道,在 Kotlin 之中可以將 Function 作為變數類型傳遞。換句話說,Function 的地位與其他的 String、Collections、SomeObject 相同,可以用來記錄狀態,同時也能將狀態傳遞給其他人,也能作為回傳值。
要開始傳遞 Function 之前,需要先了解 Function Type,這是一種 Function 的變數類型。
var action: (A, B) -> ReturnType
上面即宣告了一個變數 action ,是一個 Function Type,需要傳入兩個變數類型 A 與 B,回傳的類型是 ReturnType。
括弧內的傳入值可以不傳入任何變數,並且可以傳入最多 22 個變數(Kotlin 1.2 版與之前,Kotlin 1.3 版後可以傳入最多 255 個變數 😅)。不要回傳任何變數, 可以透過 -> Unit
即不會回傳。(像是 Java 的 void)
例如以下的 plus
變數,它是一個 Function Type,有兩個 Int 變數並回傳 Int。
印出的結果是
2 + 3 , result is 5
Kotlin 有個特性是 compiler 自動會判別變數的類型,所以宣告 String 變數可以不用明確定義變數類型,像是:
val firstName = "Jason" // implied String type
根據這個特性, plus
這個變數也可以寫成:
結果會是一樣的。
一些使用方式
將第一段的曾經出現過的 FindNumber()
作為範例,來看看要怎麼將 Function Type 作為變數傳入其他的 Function 之中。
findNumber()
第二個傳入變數為一個 Function Type,由宣告判斷將會傳入兩個變數,兩者都是 Int,並且會回傳 Int。 findNumber()
可透過 findByCondition 的各種 Function 傳入來取得目標變數,例如第一段的兩個例子便是透過 findByCondition 找出 Collection 中最大的數字與最小的數字。
再看一次第一段的範例,會發現 findNumber()
只有傳入一個變數(一個 randomNumber 的 List),沒看見括弧裡頭的 Function Type ,反而後面直接出現了 Lambda Function。
預期應該會長得這樣才對:
沒錯,不過 IDE 的 inspection info 會告訴我們,可以將括弧內部的 Lambda 表達式搬到外面去。原來,Kotlin 只要最後一個傳入變數 (最右邊) 是 Function Type,就可以搬到括弧外面,自成一個 Scope。
Kotlin 中對於傳入變數還有一個特性,我另外再舉一個例子來說明。這個例子也是隨機產生 0 至 99 間 10 個數字放入 List 之中,從裡面算出大於 50 的數字有幾個。只用 Kotlin 的 Standard Library 即可做到這個效果:
在 Kotlin 中,只要 Lambda 表達式,傳入的變數只有一個,那麼就可以使用 it 來存取。例如 count { number -> number >= 50 }
,傳入值只有一個 Int ,那麼也可以直接透過 it 來存取 number: Int。
還能透過匿名函數 (Anonymous Function),做出類似 Extension Function 的效果(先假設讀者已經知道)。例如做出為 String 加上兩側字串的 Function —— 送進一個 String,幫它加上括弧。
印出的結果將會是:
This is a !!surrounded!! word.
不過現在看起來與 Extension Function 沒什麼不同 😅。
其他應用
例如在 Android 之中,有時需要保證一些程式碼跑在
Lollipop (5.0, API 21) 以上的版本,處理這類的問題時,習慣做法是每個需要檢查得地方會包在版本檢查的 if 判斷裡頭,再決定是否執行。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODE.LOLLIPOP) {
}
如果利用 Function Type 和 Lambda 表達式則可以這樣寫:
這是設定 5.0 以上狀態列改成透明的程式碼。 runAboveLollipop(action: () -> Unit)
Function 傳入值是一個 Function Type,這個 Function 沒有傳入值,也不回傳東西。
將版本判斷放在 runAboveLollipop()
Function 之中,只有版本大於 5.0 才會執行傳進來的 Function。
未來在其他地方也需要類似的判斷時,只要這樣寫就行了:
runAboveLollipop {
// Do something above Lollipop
}
此外, Java 的 interface 像 View.OnClickListener
,只有一個 Method —— onClick(View view)
。在 Kotlin 中使用時,可以轉換成 Lambda 表達式來用 (這行為叫 SAM conversions ,SAM: single abstract method )。
view.setOnClickListener {
// it means view
}