博客地址sguotao.top/Kotlin-2018…html
在Kotlin中,高階函數是指將一個函數做爲另外一個函數的參數或者返回值。若是用f(x)、g(x)用來表示兩個函數,那麼高階函數能夠表示爲f(g(x))。Kotlin爲開發者提供了豐富的高階函數,好比Standard.kt中的let、with、apply等,_Collectioins.kt中的forEach等。爲了可以自如的使用這些高階函數,咱們有必要去了解這些高階函數的使用方法。app
在介紹常見高階函數的使用以前,有必要先了解函數類型,這對咱們理解高階函數頗有幫助。Kotlin 使用相似 (Int) -> String 的一系列函數類型來處理函數的聲明,這些類型具備與函數簽名相對應的特殊表示法,即它們的參數和返回值:less
Kotlin提供了不少高階函數,這裏根據這些高階函數所在文件的位置,分別進行介紹,先來看一下經常使用的高階函數,這些高階函數在Standard.kt文件中。函數
先來看一下TODO的源碼:學習
/** * Always throws [NotImplementedError] stating that operation is not implemented. */
@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()
/** * Always throws [NotImplementedError] stating that operation is not implemented. * * @param reason a string explaining why the implementation is missing. */
@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")
複製代碼
TODO函數有兩個重載函數,都會拋出一個NotImplementedError的異常。在Java中,有時會爲了保持業務邏輯的連貫性,對未實現的邏輯添加TODO標識,這些標識不進行處理,也不會致使程序的異常,可是在Kotlin中使用TODO時,就須要針對這些標識進行處理,不然當代碼邏輯運行到這些標識處時,就會出現程序的崩潰。ui
先給出run函數的源碼:this
/** * 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()
}
複製代碼
這兩個run函數都接收一個lambda表達式,執行傳入的lambda表達式,而且返回lambda表達式的執行結果。區別是T.run()是做爲泛型T的一個擴展函數,因此在傳入的lambda表達式中可使用this關鍵字來訪問這個泛型T中的成員變量和成員方法。spa
好比,對一個EditText控件,進行一些設置時:ssr
//email 是一個EditText控件
email.run {
this.setText("請輸入郵箱地址")
setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
}
複製代碼
先看一下with函數的源碼:code
/** * 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()
}
複製代碼
with函數有兩個參數,一個類型爲泛型T類型的receiver,和一個lambda表達式,這個表達式會做爲receiver的擴展函數來執行,而且返回lambda表達式的執行結果。
with函數與T.run函數只是寫法上的不一樣,好比上面的示例能夠用with函數:
with(email, {
setText("請輸入郵箱地址")
setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
})
//能夠進一步簡化爲
with(email) {
setText("請輸入郵箱地址")
setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
}
複製代碼
看一下apply函數的源碼:
/** * 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
}
複製代碼
apply函數做爲泛型T的擴展函數,接收一個lambda表達式,表達式的receiver是泛型T,沒有返回值,apply函數返回泛型T對象自己。能夠看到T.run()函數也是接收lambda表達式,可是返回值是lambda表達式的執行結果,這是與apply函數最大的區別。
仍是上面的示例,能夠用apply函數:
email.apply {
setText("請輸入郵箱地址")
}.apply {
setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
}.apply {
setOnClickListener {
TODO()
}
}
複製代碼
看一下also函數的源碼:
/** * 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函數相似,也是做爲泛型T的擴展函數,接收一個lambda表達式,lambda表達式沒有返回值。also函數也返回泛型T對象自己,不一樣的是also函數接收的lambda表達式須要接收一個參數T,因此在lambda表達式內部,可使用it,而apply中只能使用this。
關於this和it的區別,總結一下:
仍是上面的示例,若是用also函數:
email.also {
it.setText("請輸入郵箱地址")
}.also {
//可使用其它名稱
editView -> editView.setTextColor(applicationContext.getColor(R.color.abc_btn_colored_text_material))
}.also {
it.setOnClickListener {
//TODO
}
}
複製代碼
看一下let函數的源碼:
/** * 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函數做爲泛型T的擴展函數,接收一個lambda表達式,lambda表達式須要接收一個參數T,存在返回值。lambda表達式的返回值就是let函數的返回值。因爲lambda表達式接受參數T,因此也能夠在其內部使用it。
let應用最多的場景是用來判空,若是上面示例中的EditText是自定義的可空View,那麼使用let就很是方便:
var email: EditText? = null
TODO()
email?.let {
email.setText("請輸入郵箱地址")
email.setTextColor(getColor(R.color.abc_btn_colored_text_material))
}
複製代碼
看一下takeIf函數的源碼:
/** * 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
}
複製代碼
takeIf函數做爲泛型T的擴展函數,接受一個lambda表達式,lambda表達式接收一個參數T,返回Boolean類型,takeIf函數根據接收的lambda表達式的返回值,決定函數的返回值,若是lambda表達式返回true,函數返回T對象自己,若是lambda表達式返回false,函數返回null。
仍是上面的示例,假設用戶沒有輸入郵箱地址,進行信息提示:
email.takeIf {
email.text.isEmpty()
}?.setText("郵箱地址不能爲空")
複製代碼
給出takeUnless函數的源碼:
/** * 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
}
複製代碼
takeUnless函數與takeIf函數相似,惟一的區別是邏輯相反,takeUnless函數根據lambda表達式的返回值決定函數的返回值,若是lambda表達式返回true,函數返回null,若是lambda表達式返回false,函數返回T對象自己。
仍是上面的示例,若是用takeUnless實現,就須要調整一下邏輯:
email.takeUnless {
email.text.isNotEmpty() //與takeIf的區別
}?.setText("郵箱地址不能爲空")
複製代碼
給出repeat函數的源碼:
/** * Executes the given function [action] specified number of [times]. * * A zero-based index of current iteration is passed as a parameter to [action]. */
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
複製代碼
repeat函數接收兩個參數,一個Int型參數times表示重複次數,一個lambda表達式,lambda表達式接收一個Int型參數,無返回值。repeat函數就是將咱們傳入的lambda表達式執行times次。
repeat(3) {
println("執行第${it + 1}次")
}
//運行結果
執行第1次
執行第2次
執行第3次
複製代碼
因爲repeat函數接收的lambda表達式,須要一個Int型參數,所以在表達式內部使用it,其實it就是for循環的索引,從0開始。
最後對這些高階函數作一下總結,TODO對比Java中的TODO,須要實現業務邏輯,不能聽任不理,不然會出現異常,致使崩潰。takeIf、takeUnless這一對都是根據接收lambda表達式的返回值,決定函數的最終返回值是對象自己,仍是null,區別是takeIf,若是lambda表達式返回true,返回對象自己,不然返回null;takeUnless與takeIf的邏輯正好相反,若是lambda表達式返回true,返回null,不然返回對象自己。repeat函數,見名知意,將接收的lambda表達式重複執行指定次。
run、with、apply、also、let這幾個函數區別不是很明顯,有時候使用其中一個函數實現的邏輯,徹底也能夠用另一個函數實現,具體使用哪個,根據我的習慣。須要注意的是:
對這幾個函數的區別作一個對比:
函數名稱 | 是否做爲擴展函數 | 是否返回對象自己 | 在函數內部使用this/ it |
---|---|---|---|
run | no | no | - |
T.run | yes | no | it |
with | no | no | this |
apply | yes | yes | this |
also | yes | yes | it |
let | yes | no | it |