基本語法
people.maxBy({ p: Person -> p.age })
如果 lambda 表達(dá)式是函數(shù)調(diào)用的最后一個(gè)實(shí)參,它可以放到括號(hào)的外邊。
people.maxBy() { p: Person -> p.age }
當(dāng) lambda 是函數(shù)唯一的實(shí)參時(shí),你還可以去掉調(diào)用代碼中的空括號(hào)對(duì)。
people.maxBy { p: Person -> p.age }
如果 lambda 參數(shù)的類型可以被推導(dǎo)出來,你就不需要顯式地指定它。
people.maxBy { p -> p.age } // 推導(dǎo)出參數(shù)類型
最后簡(jiǎn)化使用默認(rèn)參數(shù)名稱it代替命名參數(shù)。如果當(dāng)前上下文期望的是只有一個(gè)參數(shù)的 lambda 且這個(gè)參數(shù)的類型可以推斷出來,就會(huì)生成這個(gè)名稱。
people.maxBy { it.age } // “it”是自動(dòng)生成的參數(shù)名稱
如果你用變量存儲(chǔ) lambda,那么就沒有可以推斷出參數(shù)類型的上下文,所以你必須顯式地指定參數(shù)類型:
>>> val getAge = { p: Person -> p.age }
>>> people.maxBy(getAge)
訪問變量
Kotlin 和 Java 的一個(gè)顯著區(qū)別就是,在 Kotlin 中你不會(huì)僅限于訪問 final 變量。在 lambda 內(nèi)部你也可以修改這些變量。
fun printProblemCounts(responses: Collection<String>) {
var clientErrors = 0 // 聲明將在 lambda 內(nèi)部訪問的變量
var serverErrors = 0
responses.forEach {
if (it.startsWith("4")) {
clientErrors++ // 在 lambda 里修改變量
} else if (it.startsWith("5")) {
serverErrors++
}
}
println("$clientErrors client errors, $serverErrors server errors")
}
成員引用
Kotlin 和 Java 8 一樣,如果你把函數(shù)轉(zhuǎn)換成一個(gè)值,你就可以傳遞它。你使用::運(yùn)算符來轉(zhuǎn)換,這種表達(dá)式稱為成員引用:
val getAge = Person::age
你還可以引用頂層函數(shù)(不是類的成員):
fun salute() = println("Salute!")
>>> run(::salute) // 引用頂層函數(shù)
Salute!
注意你還可以用同樣的方式引用擴(kuò)展函數(shù):
fun Person.isAdult() = age >= 21
val predicate = Person::isAdult
你可以用構(gòu)造方法引用存儲(chǔ)或者延期執(zhí)行創(chuàng)建類實(shí)例的動(dòng)作。構(gòu)造方法引用的形式是在雙冒號(hào)后指定類名稱
data class Person(val name: String, val age: Int)
>>> val createPerson = ::Person // 創(chuàng)建“Person”實(shí)例的動(dòng)作被保存成了值
>>> val p = createPerson("Alice", 29)
>>> println(p)
Person(name=Alice, age=29)
使用 Java 函數(shù)式接口
在 Java 中我們隨處可見這樣的代碼
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
...
}
});
在 Kotlin 中,你可以傳遞一個(gè) lambda,代替這個(gè)實(shí)例
button.setOnClickListener { view -> ... }
但是你需要注意,只有在調(diào)用 Java 中的類似函數(shù)時(shí)才能縮寫成這樣簡(jiǎn)單的lambda,如果你調(diào)用的是Kotlin 的函數(shù),你需要在 lambda 前面顯示的加上類型名字,強(qiáng)制將 lambda 轉(zhuǎn)換成對(duì)應(yīng)的接口類型
setRunnable(Runnable { println("All done!") })
OnClickListener接口只有一個(gè)抽象方法,這種接口被稱為函數(shù)式接口,或者SAM 接口,SAM 代表單抽象方法[譯注:_S_ingle _A_bstract _M_ethod]。Java API 中隨處可見像Runnable和Callable這樣的函數(shù)式接口。
當(dāng)然我們也可以通過匿名對(duì)象來實(shí)現(xiàn)
button.setOnClickListener(object : OnClickListener { // 把對(duì)象表達(dá)式作為函數(shù)式接口的實(shí)現(xiàn)傳遞
override fun onClick(View v) {
...
}
})
不過很顯然,這樣的寫法并不簡(jiǎn)潔。
還有一個(gè)重要的不同是,當(dāng)你顯式地聲明對(duì)象時(shí),每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的實(shí)例。使用 lambda 的情況不同:如果 lambda 沒有訪問任何來自定義它的函數(shù)的變量,相應(yīng)的匿名類實(shí)例可以在多次調(diào)用之間重用:
button.setOnClickListener { view -> ... }// 整個(gè)程序只會(huì)創(chuàng)建一個(gè) OnClickListener 的實(shí)例
然后,如果 lambda 從包圍它的作用域里捕捉了變量,每次調(diào)用就不再可能重用同一個(gè)實(shí)例了。這種情況下,每次調(diào)用時(shí)編譯器都要?jiǎng)?chuàng)建一個(gè)新對(duì)象,其中存儲(chǔ)著被捕捉的變量的值。
fun handleComputation(id: String) { // lambda 會(huì)捕捉”id“這個(gè)變量
postponeComputation(1000) { println(id) } // 每次 handleComputation 調(diào)用時(shí)都創(chuàng)建一個(gè) Runnable 的新實(shí)例
}
SAM 構(gòu)造方法顯式地把 lambda 轉(zhuǎn)換成函數(shù)式接口
fun createAllDoneRunnable(): Runnable {
return Runnable { println("All done!") }
}
>>> createAllDoneRunnable().run()
All done!
val listener = OnClickListener { view ->
val text = when (view.id) { // 使用 view.id 來判斷點(diǎn)擊的是哪個(gè)按鈕
R.id.button1 -> "First button"
R.id.button2 -> "Second button"
else -> "Unknown button"
}
toast(text) // 把“text”的值顯示給用戶
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
“with”函數(shù)
來看一個(gè)例子:
fun alphabet(): String {
val result = StringBuilder()
for (letter in 'A'..'Z') {
result.append(letter)
}
result.append("\nNow I know the alphabet!")
return result.toString()
}
>>> println(alphabet())
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Now I know the alphabet!
使用 with函數(shù)改造
fun alphabet(): String {
val stringBuilder = StringBuilder()
return with(stringBuilder) { // 指定接收者的值,你會(huì)調(diào)用它的方法
for (letter in 'A'..'Z') {
this.append(letter) // 通過顯式的“this”來調(diào)用接收者值的方法
}
append("\nNow I know the alphabet!") // 省掉“this”也可以調(diào)用方法,
this.toString() // 從 lambda 返回值
}
}
進(jìn)一步改造
fun alphabet() = with(StringBuilder()) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
toString()
}
with返回的值是執(zhí)行 lambda 代碼的結(jié)果。該結(jié)果就是 lambda 里的最后一個(gè)表達(dá)式(的值)。
但有時(shí)候你想返回的是接收者對(duì)象,而不是執(zhí)行 lambda 的結(jié)果。這時(shí)apply庫函數(shù)就派上用場(chǎng)了。
“apply”函數(shù)
apply函數(shù)幾乎和with函數(shù)一模一樣;唯一的區(qū)別是apply始終會(huì)返回作為實(shí)參傳遞給它的對(duì)象(換句話說,接收者對(duì)象)。讓我們?cè)僖淮沃貥?gòu)alphabet函數(shù),這一次用的是apply。
fun alphabet() = StringBuilder().apply {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
}.toString()
另外,在kotlin標(biāo)準(zhǔn)庫中查看apply函數(shù)的源碼
inline fun <T> T.apply(block: T.() -> Unit): T
你會(huì)發(fā)現(xiàn)T.() -> Unit,我們平時(shí)定義函數(shù)類型參數(shù)難道不是() -> Unit嗎?
這里是有區(qū)別的,前者表示帶有接受者的函數(shù)類型,也就是在這個(gè)函數(shù)內(nèi)部的this表示的是接受者實(shí)例。而后者使用的this表示包含這個(gè)函數(shù)的外部類。