1. 基礎(chǔ)定義
1.1 什么是高階函數(shù)
按照定義维咸,高階函數(shù)就是以另外一個(gè)函數(shù)作為參數(shù)或者返回值的函數(shù)。
在Kotlin中惠爽,函數(shù)可以用lambda或者函數(shù)引用來(lái)表示癌蓖。
因此,任何以lambda
或者函數(shù)引用作為參數(shù)的函數(shù)婚肆,或者返回值為lambda
或者函數(shù)引用的函數(shù)租副,或者兩者都滿(mǎn)足的函數(shù)都是高階函數(shù)。
1.2 lambda的約定:
要熟悉Kotlin函數(shù)较性,首先得看懂代碼中的lambda表達(dá)式用僧,這里首先就得清楚一些約定,如:
當(dāng)函數(shù)中只有一個(gè)函數(shù)作為參數(shù)赞咙,并且使用了lambda
表達(dá)式作為對(duì)應(yīng)的參數(shù)责循,那么可以省略函數(shù)的小括號(hào)()
。
函數(shù)的最后一個(gè)參數(shù)是函數(shù)類(lèi)型時(shí)攀操,可以使用lambda表達(dá)式將函數(shù)參數(shù)寫(xiě)在參數(shù)列表括號(hào)外面院仿。
例如:
str.sumBy( { it.toInt } )
可以省略成
str.sumBy{ it.toInt }
Anko的Context擴(kuò)展alert函數(shù),可以注意到positiveButton方法第一個(gè)參數(shù)是text速和,
第二個(gè)參數(shù)是監(jiān)聽(tīng)器lambda表達(dá)式歹垫,寫(xiě)在了參數(shù)列表圓括號(hào)外面。
alert("確定刪除嗎颠放?","Alert") {
positiveButton("OK") { Log.i(TAG, "你點(diǎn)了確定按鈕")}
negativeButton("Cancel") { Log.i(TAG, "你點(diǎn)了取消按鈕") }
}.build().show()
1.3 函數(shù)類(lèi)型變量與對(duì)應(yīng)的Java代碼
在Kotlin中县钥,變量的類(lèi)型可以是函數(shù)類(lèi)型
,例如下面的代碼中sum
變量的類(lèi)型是Int類(lèi)型
慈迈,而predicate
變量是函數(shù)類(lèi)型
若贮,也就是說(shuō)這個(gè)變量代表一個(gè)函數(shù)。
聲明一個(gè)名字為sum的Int類(lèi)型變量(這個(gè)sum變量的類(lèi)型是Int)
var sum:Int
聲明一個(gè)名字為predicate的函數(shù)類(lèi)型變量(這個(gè)predicate變量的類(lèi)型是函數(shù))
predicate是一個(gè)以Char為參數(shù)痒留,返回值為Boolean的函數(shù)谴麦。
var predicate: (Char) -> Boolean
聲明一個(gè)以predicate函數(shù)為參數(shù)的函數(shù)(高階函數(shù)),這個(gè)函數(shù)的返回類(lèi)型是String
fun filter(predicate: (Char) -> Boolean) :String
讓上面這個(gè)函數(shù)帶上接受者伸头,其實(shí)就是給String聲明了一個(gè)擴(kuò)展函數(shù)匾效。
帶上了接收者的函數(shù),函數(shù)內(nèi)部可以直接訪問(wèn)String的其他方法屬性恤磷,相當(dāng)于函數(shù)內(nèi)部的this就是String
fun String.filter(predicate: (char) -> Boolean) :String
Kotlin和Java代碼是可以混合調(diào)用的面哼,因此Kotlin的函數(shù)引用在Java是有一種對(duì)應(yīng)的形式野宜,那就是Function
引用,Function1<P, R>
代表只有一個(gè)參數(shù)類(lèi)型為P的返回值類(lèi)型為R的引用魔策。
2. 標(biāo)準(zhǔn)高階函數(shù)
2.1 標(biāo)準(zhǔn)高階函數(shù)的聲明
標(biāo)準(zhǔn)高階函數(shù)聲明在Standard.kt
文件中匈子,其中有TODO
、run
闯袒、with
虎敦、apply
、also
政敢、let
其徙、takeIf
、takeUnless
喷户、repeat
函數(shù)唾那。
我們將功能類(lèi)似的函數(shù)放在一塊對(duì)比,如run & with
褪尝、apply & also
通贞、takeIf & takeUnless
、let & 擴(kuò)展函數(shù)版本run
恼五。
2.2 run&with函數(shù)
/**
* Calls the specified function [block] and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
/**
* Calls the specified function [block] with `this` value as its receiver and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
/**
* Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
run函數(shù)的版本有兩個(gè)版本昌罩,一個(gè)是普通版本的定義,一種是擴(kuò)展函數(shù)版本
從代碼定義可以看到灾馒,run函數(shù)
接受一個(gè)函數(shù)引用作為參數(shù)(高階函數(shù))茎用,在內(nèi)部?jī)H僅只是調(diào)用了一下這個(gè)代碼塊并且返回block代碼塊的返回值。
可以發(fā)現(xiàn)with
和run
都是返回了block
(是個(gè)函數(shù)引用)的返回值睬罗。
區(qū)別在哪:
區(qū)別在于有個(gè)run是擴(kuò)展函數(shù)轨功,如果在使用之前需要判空,那么擴(kuò)展函數(shù)版本的run函數(shù)
的使用會(huì)比with函數(shù)
優(yōu)雅容达,如:
// Yack!
with(webview.settings) {
this?.javaScriptEnabled = true
this?.databaseEnabled = true
}
// Nice.
webview.settings?.run {
javaScriptEnabled = true
databaseEnabled = true
}
可以看到擴(kuò)展函數(shù)版本的run函數(shù)
在調(diào)用前可以先判斷webview.settings是否為空古涧,否則不進(jìn)入函數(shù)體調(diào)用。
2.3 apply&also
/**
* Calls the specified function [block] with `this` value as its receiver and returns `this` value.
*/
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
/**
* Calls the specified function [block] with `this` value as its argument and returns `this` value.
*/
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
apply
&also
最后都會(huì)返回接收者自身T
花盐,所以可以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用細(xì)化代碼粒度羡滑,讓代碼更清晰,它們的區(qū)別是一個(gè)apply
的block
是用T(也就是apply的接收者本身)
作為接收者算芯,因此在apply
的block
內(nèi)部可以訪問(wèn)到T這個(gè)this
柒昏,also
的T
是被當(dāng)做參數(shù)傳入block
的,所以在also
的block
內(nèi)部需要用it(lambda的唯一參數(shù))
代表這個(gè)also
的接收者T
熙揍。
使用上:
一般來(lái)說(shuō)职祷,lambda
參數(shù)為it
的函數(shù)比較適合用做讀值多的場(chǎng)合,也可以使用命名參數(shù)將it
改成合適的名字提升代碼可讀性,將T
傳入block(T)
的場(chǎng)合比較適合用于寫(xiě)值有梆,因?yàn)槭×撕芏郥變量的重復(fù)聲明是尖。
【推薦】lambda表達(dá)式的block中,如果主要進(jìn)行對(duì)某個(gè)實(shí)例的寫(xiě)操作泥耀,則該實(shí)例聲明為Receiver
饺汹;如果主要是讀操作,則該實(shí)例聲明為參數(shù)爆袍。
所以在寫(xiě)值操作多時(shí)使用apply代碼塊,在讀值操作多時(shí)使用also代碼塊作郭。
inline fun <T> T.apply(block: T.() -> Unit): T//對(duì)T進(jìn)行寫(xiě)操作陨囊,優(yōu)先使用apply
tvName.apply {
text = "Jacky"
textSize = 20f
}
inline fun <T> T.also(block: (T) -> Unit): T //對(duì)T進(jìn)行讀操作 優(yōu)先使用also
user.also {
tvName.text = it.name
tvAge.text = it.age
}
2.4 let函數(shù)
/**
* Calls the specified function [block] with `this` value as its receiver and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
/**
* Calls the specified function [block] with `this` value as its argument and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
let
這個(gè)函數(shù)和擴(kuò)展版本的run函數(shù)
非常像,所以在上面的代碼我把它們放在一起對(duì)比夹攒,他們的區(qū)別在run
的block參數(shù)
是個(gè)帶run接收者T的函數(shù)引用
蜘醋,而let
的block參數(shù)是
把let的接收者T
當(dāng)做參數(shù)傳給block
,因此他們的調(diào)用區(qū)別是使用run
時(shí)咏尝,block
內(nèi)部的this
是指向T
的压语,而在let
的block
內(nèi)部需要使用it
來(lái)指向T
,let
的block
內(nèi)部的this
指的是T
外部的this
编检,意思是類(lèi)似于你在Activity
里面用let
胎食,let
的block
里面的this
就是這個(gè)Activity實(shí)例
。
run
函數(shù)比較適合寫(xiě)值多的代碼塊允懂,let
函數(shù)比較適合讀值多的代碼塊厕怜。
2.4.1 let與also的返回值區(qū)別
let
返回的是block的返回值
,also
返回的是接收者T自身
蕾总,因此他們的鏈?zhǔn)秸{(diào)用有本質(zhì)區(qū)別粥航。
let
能實(shí)現(xiàn)類(lèi)似RxJava
的map
的效果
val original = "abc"
// Evolve the value and send to the next chain
original.let {
println("The original String is $it") // "abc"
it.reversed() // evolve it as parameter to send to next let
}.let {
println("The reverse String is $it") // "cba"
it.length // can be evolve to other type
}.let {
println("The length of the String is $it") // 3
}
// Wrong
// Same value is sent in the chain (printed answer is wrong)
original.also {
println("The original String is $it") // "abc"
it.reversed() // even if we evolve it, it is useless
}.also {
println("The reverse String is ${it}") // "abc"
it.length // even if we evolve it, it is useless
}.also {
println("The length of the String is ${it}") // "abc"
}
// Corrected for also (i.e. manipulate as original string
// Same value is sent in the chain
original.also {
println("The original String is $it") // "abc"
}.also {
println("The reverse String is ${it.reversed()}") // "cba"
}.also {
println("The length of the String is ${it.length}") // 3
}
在上面看來(lái)T.also
好像毫無(wú)意義,因?yàn)槲覀兛梢院苋菀椎貙⑺鼈兘M合成一個(gè)功能塊生百。但仔細(xì)想想递雀,它也有一些優(yōu)點(diǎn):
它可以在相同的對(duì)象上提供一個(gè)非常清晰的分離過(guò)程,即制作更小的功能部分蚀浆。
在使用之前缀程,它可以實(shí)現(xiàn)非常強(qiáng)大的自我操縱,實(shí)現(xiàn)鏈條建設(shè)者操作(builder 模式)市俊。
2.5 takeIf&takeUnless
/**
* Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
*/
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (predicate(this)) this else null
}
/**
* Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does.
*/
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (!predicate(this)) this else null
}
這兩個(gè)函數(shù)是用來(lái)做有條件判斷時(shí)使用的杠输,takeIf
是在predicate
條件返回true
時(shí)返回接收者自身,否者返回null
秕衙,takeUnless
則剛好相反蠢甲,是在predicate
為false
時(shí)返回接收者自身,否則返回null
据忘。
2.6 repeat函數(shù)
/**
* Executes the given function [action] specified number of [times].
*
* A zero-based index of current iteration is passed as a parameter to [action].
*
* @sample samples.misc.ControlFlow.repeat
*/
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
這個(gè)函數(shù)就是把action
代碼塊重復(fù)執(zhí)行times
次鹦牛,action
的參數(shù)就是當(dāng)前執(zhí)行的index
(第幾次)搞糕。
2.7 This vs. it參數(shù)
如果你檢查T.run
函數(shù)簽名,你會(huì)注意到T.run
只是作為擴(kuò)展函數(shù)調(diào)用block: T.()
曼追。因此窍仰,所有的范圍內(nèi),T
可以被稱(chēng)為this
礼殊。在編程中驹吮,this
大部分時(shí)間可以省略。因此晶伦,在我們上面的例子中碟狞,我們可以在println
聲明中使用$length
,而不是${this.length}
婚陪。我把這稱(chēng)為傳遞this
參數(shù)族沃。
然而,對(duì)于T.let
函數(shù)簽名泌参,你會(huì)注意到T.let
把自己作為參數(shù)傳遞進(jìn)去脆淹,即block: (T)
。因此沽一,這就像傳遞一個(gè)lambda
參數(shù)盖溺。它可以在作用域范圍內(nèi)使用it
作為引用。所以我把這稱(chēng)為傳遞it
參數(shù)铣缠。
從上面看咐柜,它似乎T.run
是更優(yōu)越,因?yàn)?code>T.let更隱含攘残,但是這是T.let
函數(shù)有一些微妙的優(yōu)勢(shì)如下:
T.let
相比外部類(lèi)函數(shù)/成員拙友,使用給定的變量函數(shù)/成員提供了更清晰的區(qū)分
在this
不能被省略的情況下,例如當(dāng)它作為函數(shù)的參數(shù)被傳遞時(shí)it
比this
更短歼郭,更清晰遗契。
在T.let
允許使用更好的變量命名,你可以轉(zhuǎn)換it
為其他名稱(chēng)病曾。
stringVariable?.let {
nonNullString ->
println("The non null string is $nonNullString")
}
2.8 這幾個(gè)函數(shù)的選擇:
調(diào)用鏈中保持原類(lèi)型(T -> T) | 調(diào)用鏈中轉(zhuǎn)換為其他類(lèi)型(T -> R) | 調(diào)用鏈起始(考慮使用) | 調(diào)用鏈中應(yīng)用條件語(yǔ)句 | |
---|---|---|---|---|
多寫(xiě)操作 | T.apply { ... } | T.run{ ... } | with(T) { ... } | T.takeIf/T.takeUnless |
多讀操作 | T.also { ... } | T.let{ ... } |
3. 自定義高階函數(shù)
3.1 debug環(huán)境才運(yùn)行的代碼
//聲明:
inline fun debug(code: () -> Unit){
if (BuildConfig.DEBUG) {
code()
}
}
//用法:
fun onCreate(savedInstanceState: Bundle?) {
debug {
showDebugTools();
}
}
函數(shù)聲明為inline
內(nèi)聯(lián)則會(huì)在編譯時(shí)將代碼復(fù)制粘貼到對(duì)應(yīng)調(diào)用的地方牍蜂,如果函數(shù)體很大很復(fù)雜,不建議使用內(nèi)聯(lián)泰涂,否則會(huì)使包體積增大鲫竞。
4. Anko相關(guān)介紹
4.1 Anko庫(kù)顯示一個(gè)標(biāo)準(zhǔn)的對(duì)話(huà)框
alert("確定刪除嗎?","Alert") {
positiveButton("OK") { Log.i(TAG, "你點(diǎn)了確定按鈕")}
negativeButton("Cancel") { Log.i(TAG, "你點(diǎn)了取消按鈕") }
}.build().show()
4.2 Anko庫(kù)包含的幾個(gè)部分逼蒙。
Anko consists of several parts:
- Anko Commons: a lightweight library full of helpers for intents, dialogs, logging and so on;
- Anko Layouts: a fast and type-safe way to write dynamic Android layouts;
- Anko SQLite: a query DSL and parser collection for Android SQLite;
- Anko Coroutines: utilities based on the [kotlinx.coroutines]
公共部分:intents从绘,dialogs, logging等高階函數(shù)。
布局部分:動(dòng)態(tài)擴(kuò)展函數(shù)快速添加layout,如4.1顯示的標(biāo)準(zhǔn)對(duì)話(huà)框僵井。
SQLite部分:查詢(xún)解析集合DSL
協(xié)程部分:協(xié)程相關(guān)工具陕截。
4.2.1 Anko Layouts (wiki)
Anko Layouts is a DSL for writing dynamic Android layouts. Here is a simple UI written with Anko DSL:
verticalLayout {
val name = editText()
button("Say Hello") {
onClick { toast("Hello, ${name.text}!") }
}
}
4.2.2 Anko SQLite (wiki)
Have you ever been tired of parsing SQLite query results using Android cursors? Anko SQLite provides lots of helpers to simplify working with SQLite databases.
For example, here is how you can fetch the list of users with a particular name:
fun getUsers(db: ManagedSQLiteOpenHelper): List<User> = db.use {
db.select("Users")
.whereSimple("family_name = ?", "John")
.doExec()
.parseList(UserParser)
}
詳細(xì)的看Anko主頁(yè)
5. 參考資源
Mastering Kotlin standard functions: run, with, let, also and apply
掌握Kotlin標(biāo)準(zhǔn)函數(shù):run, with, let, also and apply
Anko: https://github.com/Kotlin/anko