基本語(yǔ)法
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)出來(lái)腮出,你就不需要顯式地指定它帖鸦。
people.maxBy { p -> p.age } // 推導(dǎo)出參數(shù)類型
最后簡(jiǎn)化使用默認(rèn)參數(shù)名稱it
代替命名參數(shù)。如果當(dāng)前上下文期望的是只有一個(gè)參數(shù)的 lambda 且這個(gè)參數(shù)的類型可以推斷出來(lái)胚嘲,就會(huì)生成這個(gè)名稱作儿。
people.maxBy { it.age } // “it”是自動(dòng)生成的參數(shù)名稱
如果你用變量存儲(chǔ) lambda,那么就沒(méi)有可以推斷出參數(shù)類型的上下文馋劈,所以你必須顯式地指定參數(shù)類型:
>>> val getAge = { p: Person -> p.age }
>>> people.maxBy(getAge)
訪問(wèn)變量
Kotlin 和 Java 的一個(gè)顯著區(qū)別就是攻锰,在 Kotlin 中你不會(huì)僅限于訪問(wèn) final 變量。在 lambda 內(nèi)部你也可以修改這些變量妓雾。
fun printProblemCounts(responses: Collection<String>) {
var clientErrors = 0 // 聲明將在 lambda 內(nèi)部訪問(wèn)的變量
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)算符來(lái)轉(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 中我們隨處可見(jiàn)這樣的代碼
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
...
}
});
在 Kotlin 中策添,你可以傳遞一個(gè) lambda材部,代替這個(gè)實(shí)例
button.setOnClickListener { view -> ... }
但是你需要注意,只有在調(diào)用 Java 中的類似函數(shù)時(shí)才能縮寫(xiě)成這樣簡(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 中隨處可見(jiàn)像Runnable
和Callable
這樣的函數(shù)式接口棵磷。
當(dāng)然我們也可以通過(guò)匿名對(duì)象來(lái)實(shí)現(xiàn)
button.setOnClickListener(object : OnClickListener { // 把對(duì)象表達(dá)式作為函數(shù)式接口的實(shí)現(xiàn)傳遞
override fun onClick(View v) {
...
}
})
不過(guò)很顯然,這樣的寫(xiě)法并不簡(jiǎn)潔晋涣。
還有一個(gè)重要的不同是仪媒,當(dāng)你顯式地聲明對(duì)象時(shí),每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的實(shí)例谢鹊。使用 lambda 的情況不同:如果 lambda 沒(méi)有訪問(wèn)任何來(lái)自定義它的函數(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 來(lái)判斷點(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ù)
來(lái)看一個(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) // 通過(guò)顯式的“this”來(lái)調(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
庫(kù)函數(shù)就派上用場(chǎng)了。
“apply”函數(shù)
apply
函數(shù)幾乎和with
函數(shù)一模一樣似谁;唯一的區(qū)別是apply
始終會(huì)返回作為實(shí)參傳遞給它的對(duì)象(換句話說(shuō)傲绣,接收者對(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)庫(kù)中查看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ù)的外部類。