Kotlin中的高階函數(shù)深入講解
前言
在Kotlin中,高階函數(shù)是指將一個(gè)函數(shù)作為另一個(gè)函數(shù)的參數(shù)或者返回值。如果用f(x)、g(x)用來(lái)表示兩個(gè)函數(shù),那么高階函數(shù)可以表示為f(g(x))。Kotlin為開(kāi)發(fā)者提供了豐富的高階函數(shù),比如Standard.kt中的let、with、apply等,_Collectioins.kt中的forEach等。為了能夠自如的使用這些高階函數(shù),我們有必要去了解這些高階函數(shù)的使用方法。
函數(shù)類型
在介紹常見(jiàn)高階函數(shù)的使用之前,有必要先了解函數(shù)類型,這對(duì)我們理解高階函數(shù)很有幫助。Kotlin 使用類似 (Int) -> String 的一系列函數(shù)類型來(lái)處理函數(shù)的聲明,這些類型具有與函數(shù)簽名相對(duì)應(yīng)的特殊表示法,即它們的參數(shù)和返回值:
- 所有函數(shù)類型都有一個(gè)圓括號(hào)括起來(lái)的參數(shù)類型列表以及一個(gè)返回類型:(A, B) -> C 表示接受類型分別為 A 與 B 兩個(gè)參數(shù)并返回一個(gè) C類型值的函數(shù)類型。參數(shù)類型列表可以為空,如 () -> A ,返回值為空,如(A, B) -> Unit;
- 函數(shù)類型可以有一個(gè)額外的接收者類型,它在表示法中的點(diǎn)之前指定,如類型 A.(B) -> C 表示可以在 A 的接收者對(duì)象上,調(diào)用一個(gè)以 B 類型作為參數(shù),并返回一個(gè) C 類型值的函數(shù)。
- 還有一種比較特殊的函數(shù)類型,掛起函數(shù),它的表示法中有一個(gè) suspend 修飾符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C 。
常用高階函數(shù)
Kotlin提供了很多高階函數(shù),這里根據(jù)這些高階函數(shù)所在文件的位置,分別進(jìn)行介紹,先來(lái)看一下常用的高階函數(shù),這些高階函數(shù)在Standard.kt文件中。
1.TODO
先來(lái)看一下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函數(shù)有兩個(gè)重載函數(shù),都會(huì)拋出一個(gè)NotImplementedError的異常。在Java中,有時(shí)會(huì)為了保持業(yè)務(wù)邏輯的連貫性,對(duì)未實(shí)現(xiàn)的邏輯添加TODO標(biāo)識(shí),這些標(biāo)識(shí)不進(jìn)行處理,也不會(huì)導(dǎo)致程序的異常,但是在Kotlin中使用TODO時(shí),就需要針對(duì)這些標(biāo)識(shí)進(jìn)行處理,否則當(dāng)代碼邏輯運(yùn)行到這些標(biāo)識(shí)處時(shí),就會(huì)出現(xiàn)程序的崩潰。
2.run
先給出run函數(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()
}
這兩個(gè)run函數(shù)都接收一個(gè)lambda表達(dá)式,執(zhí)行傳入的lambda表達(dá)式,并且返回lambda表達(dá)式的執(zhí)行結(jié)果。區(qū)別是T.run()是作為泛型T的一個(gè)擴(kuò)展函數(shù),所以在傳入的lambda表達(dá)式中可以使用this關(guān)鍵字來(lái)訪問(wèn)這個(gè)泛型T中的成員變量和成員方法。
比如,對(duì)一個(gè)EditText控件,進(jìn)行一些設(shè)置時(shí):
//email 是一個(gè)EditText控件
email.run {
this.setText("請(qǐng)輸入郵箱地址")
setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
}
3.with
先看一下with函數(shù)的源碼:
/**
* 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函數(shù)有兩個(gè)參數(shù),一個(gè)類型為泛型T類型的receiver,和一個(gè)lambda表達(dá)式,這個(gè)表達(dá)式會(huì)作為receiver的擴(kuò)展函數(shù)來(lái)執(zhí)行,并且返回lambda表達(dá)式的執(zhí)行結(jié)果。
with函數(shù)與T.run函數(shù)只是寫(xiě)法上的不同,比如上面的示例可以用with函數(shù):
with(email, {
setText("請(qǐng)輸入郵箱地址")
setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
})
//可以進(jìn)一步簡(jiǎn)化為
with(email) {
setText("請(qǐng)輸入郵箱地址")
setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
}
4.apply
看一下apply函數(shù)的源碼:
/**
* 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函數(shù)作為泛型T的擴(kuò)展函數(shù),接收一個(gè)lambda表達(dá)式,表達(dá)式的receiver是泛型T,沒(méi)有返回值,apply函數(shù)返回泛型T對(duì)象本身??梢钥吹絋.run()函數(shù)也是接收l(shuí)ambda表達(dá)式,但是返回值是lambda表達(dá)式的執(zhí)行結(jié)果,這是與apply函數(shù)最大的區(qū)別。
還是上面的示例,可以用apply函數(shù):
email.apply {
setText("請(qǐng)輸入郵箱地址")
}.apply {
setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
}.apply {
setOnClickListener {
TODO()
}
}
5.also
看一下also函數(shù)的源碼:
/**
* 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函數(shù)類似,也是作為泛型T的擴(kuò)展函數(shù),接收一個(gè)lambda表達(dá)式,lambda表達(dá)式?jīng)]有返回值。also函數(shù)也返回泛型T對(duì)象本身,不同的是also函數(shù)接收的lambda表達(dá)式需要接收一個(gè)參數(shù)T,所以在lambda表達(dá)式內(nèi)部,可以使用it,而apply中只能使用this。
關(guān)于this和it的區(qū)別,總結(jié)一下:
- 如果泛型T,作為lambda表達(dá)式的參數(shù),形如:(T) -> Unit,此時(shí)在lambda表示內(nèi)部使用it;
- 如果泛型T,作為lambda表達(dá)式的接收者,形如:T.() -> Unit,此時(shí)在lambda表達(dá)式內(nèi)部使用this;
- 不論this,還是it,都代表T對(duì)象,區(qū)別是it可以使用其它的名稱代替。
還是上面的示例,如果用also函數(shù):
email.also {
it.setText("請(qǐng)輸入郵箱地址")
}.also {
//可以使用其它名稱
editView -> editView.setTextColor(applicationContext.getColor(R.color.abc_btn_colored_text_material))
}.also {
it.setOnClickListener {
//TODO
}
}
6.let
看一下let函數(shù)的源碼:
/**
* 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函數(shù)作為泛型T的擴(kuò)展函數(shù),接收一個(gè)lambda表達(dá)式,lambda表達(dá)式需要接收一個(gè)參數(shù)T,存在返回值。lambda表達(dá)式的返回值就是let函數(shù)的返回值。由于lambda表達(dá)式接受參數(shù)T,所以也可以在其內(nèi)部使用it。
let應(yīng)用最多的場(chǎng)景是用來(lái)判空,如果上面示例中的EditText是自定義的可空View,那么使用let就非常方便:
var email: EditText? = null
TODO()
email?.let {
email.setText("請(qǐng)輸入郵箱地址")
email.setTextColor(getColor(R.color.abc_btn_colored_text_material))
}
7.takeIf
看一下takeIf函數(shù)的源碼:
/**
* 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函數(shù)作為泛型T的擴(kuò)展函數(shù),接受一個(gè)lambda表達(dá)式,lambda表達(dá)式接收一個(gè)參數(shù)T,返回Boolean類型,takeIf函數(shù)根據(jù)接收的lambda表達(dá)式的返回值,決定函數(shù)的返回值,如果lambda表達(dá)式返回true,函數(shù)返回T對(duì)象本身,如果lambda表達(dá)式返回false,函數(shù)返回null。
還是上面的示例,假設(shè)用戶沒(méi)有輸入郵箱地址,進(jìn)行信息提示:
email.takeIf {
email.text.isEmpty()
}?.setText("郵箱地址不能為空")
8.takeUnless
給出takeUnless函數(shù)的源碼:
/**
* 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函數(shù)與takeIf函數(shù)類似,唯一的區(qū)別是邏輯相反,takeUnless函數(shù)根據(jù)lambda表達(dá)式的返回值決定函數(shù)的返回值,如果lambda表達(dá)式返回true,函數(shù)返回null,如果lambda表達(dá)式返回false,函數(shù)返回T對(duì)象本身。
還是上面的示例,如果用takeUnless實(shí)現(xiàn),就需要調(diào)整一下邏輯:
email.takeUnless {
email.text.isNotEmpty() //與takeIf的區(qū)別
}?.setText("郵箱地址不能為空")
9.repeat
給出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].
*/
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
repeat函數(shù)接收兩個(gè)參數(shù),一個(gè)Int型參數(shù)times表示重復(fù)次數(shù),一個(gè)lambda表達(dá)式,lambda表達(dá)式接收一個(gè)Int型參數(shù),無(wú)返回值。repeat函數(shù)就是將我們傳入的lambda表達(dá)式執(zhí)行times次。
repeat(3) {
println("執(zhí)行第${it + 1}次")
}
//運(yùn)行結(jié)果
執(zhí)行第1次
執(zhí)行第2次
執(zhí)行第3次
由于repeat函數(shù)接收的lambda表達(dá)式,需要一個(gè)Int型參數(shù),因此在表達(dá)式內(nèi)部使用it,其實(shí)it就是for循環(huán)的索引,從0開(kāi)始。
總結(jié)
最后對(duì)這些高階函數(shù)做一下總結(jié),TODO對(duì)比Java中的TODO,需要實(shí)現(xiàn)業(yè)務(wù)邏輯,不能放任不理,否則會(huì)出現(xiàn)異常,導(dǎo)致崩潰。takeIf、takeUnless這一對(duì)都是根據(jù)接收l(shuí)ambda表達(dá)式的返回值,決定函數(shù)的最終返回值是對(duì)象本身,還是null,區(qū)別是takeIf,如果lambda表達(dá)式返回true,返回對(duì)象本身,否則返回null;takeUnless與takeIf的邏輯正好相反,如果lambda表達(dá)式返回true,返回null,否則返回對(duì)象本身。repeat函數(shù),見(jiàn)名知意,將接收的lambda表達(dá)式重復(fù)執(zhí)行指定次。
run、with、apply、also、let這幾個(gè)函數(shù)區(qū)別不是很明顯,有時(shí)候使用其中一個(gè)函數(shù)實(shí)現(xiàn)的邏輯,完全也可以用另外一個(gè)函數(shù)實(shí)現(xiàn),具體使用哪一個(gè),根據(jù)個(gè)人習(xí)慣。需要注意的是:
- 對(duì)作為擴(kuò)展函數(shù)的高階函數(shù),使用前需要判斷接收的對(duì)象是否為空,比如T.run,apply,also,let在使用前需要進(jìn)行空檢查;
- 對(duì)于返回對(duì)象本身的函數(shù),比如apply,also可以形成鏈?zhǔn)秸{(diào)用;
- 對(duì)于在函數(shù)內(nèi)部能夠使用it的函數(shù),it可以用意思更加清晰的變量代替,比如T.run,also,let。
對(duì)這幾個(gè)函數(shù)的區(qū)別做一個(gè)對(duì)比:
| 函數(shù)名稱 | 是否作為擴(kuò)展函數(shù) | 是否返回對(duì)象本身 | 在函數(shù)內(nèi)部使用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 |
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
android通過(guò)拼音搜索中文的功能實(shí)現(xiàn)代碼
這篇文章主要介紹了android通過(guò)拼音搜索中文的功能實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
你值得擁有的Android Studio開(kāi)發(fā)小技巧
這篇文章主要為大家分享了值得擁有的Android Studio開(kāi)發(fā)小技巧,介紹幾個(gè)比較好用的技巧和快捷鍵,提升我們的編碼效率,感興趣的小伙伴們可以參考一下2016-06-06
Android onActivityResult和setResult方法詳解及使用
這篇文章主要介紹了Android onActivityResult和setResult方法詳解及使用的相關(guān)資料,這里提供實(shí)例,幫助大家學(xué)習(xí)理解,需要的朋友可以參考下2016-12-12
Webview實(shí)現(xiàn)android簡(jiǎn)單的瀏覽器實(shí)例代碼
這篇文章主要介紹了Webview實(shí)現(xiàn)android簡(jiǎn)單的瀏覽器實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-02-02
Android使用ViewDragHelper實(shí)現(xiàn)QQ6.X最新版本側(cè)滑界面效果實(shí)例代碼
這篇文章主要介紹了Android程序開(kāi)發(fā)實(shí)現(xiàn)QQ6.X最新版本側(cè)滑界面效果實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-02-02
Android沉浸式狀態(tài)欄 + actionBar漸變 + scrollView頂部伸縮效果
這篇文章主要介紹了Android沉浸式狀態(tài)欄 + actionBar漸變 + scrollView頂部伸縮效果即QQ好友動(dòng)態(tài)頁(yè)面效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-12-12

