Kotlin中的高阶函数
博客地址sguotao.top/Kotlin-2018…
在Kotlin中,高階函數是指將一個函數作為另一個函數的參數或者返回值。如果用f(x)、g(x)用來表示兩個函數,那么高階函數可以表示為f(g(x))。Kotlin為開發者提供了豐富的高階函數,比如Standard.kt中的let、with、apply等,_Collectioins.kt中的forEach等。為了能夠自如的使用這些高階函數,我們有必要去了解這些高階函數的使用方法。
函數類型
在介紹常見高階函數的使用之前,有必要先了解函數類型,這對我們理解高階函數很有幫助。Kotlin 使用類似 (Int) -> String 的一系列函數類型來處理函數的聲明,這些類型具有與函數簽名相對應的特殊表示法,即它們的參數和返回值:
- 所有函數類型都有一個圓括號括起來的參數類型列表以及一個返回類型:(A, B) -> C 表示接受類型分別為 A 與 B 兩個參數并返回一個 C類型值的函數類型。參數類型列表可以為空,如 () -> A ,返回值為空,如(A, B) -> Unit;
- 函數類型可以有一個額外的接收者類型,它在表示法中的點之前指定,如類型 A.(B) -> C 表示可以在 A 的接收者對象上,調用一個以 B 類型作為參數,并返回一個 C 類型值的函數。
- 還有一種比較特殊的函數類型,掛起函數,它的表示法中有一個 suspend 修飾符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C 。
常用高階函數
Kotlin提供了很多高階函數,這里根據這些高階函數所在文件的位置,分別進行介紹,先來看一下常用的高階函數,這些高階函數在Standard.kt文件中。
1.TODO
先來看一下TODO的源碼:
/*** Always throws [NotImplementedError] stating that operation is not implemented.*/.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.*/ .internal.InlineOnly public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason") 復制代碼TODO函數有兩個重載函數,都會拋出一個NotImplementedError的異常。在Java中,有時會為了保持業務邏輯的連貫性,對未實現的邏輯添加TODO標識,這些標識不進行處理,也不會導致程序的異常,但是在Kotlin中使用TODO時,就需要針對這些標識進行處理,否則當代碼邏輯運行到這些標識處時,就會出現程序的崩潰。
2.run
先給出run函數的源碼:
/*** Calls the specified function [block] and returns its result.*/ .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.*/ .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中的成員變量和成員方法。
比如,對一個EditText控件,進行一些設置時:
//email 是一個EditText控件 email.run { this.setText("請輸入郵箱地址")setTextColor(context.getColor(R.color.abc_btn_colored_text_material)) } 復制代碼3.with
先看一下with函數的源碼:
/*** Calls the specified function [block] with the given [receiver] as its receiver and returns its result.*/ .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))} 復制代碼4.apply
看一下apply函數的源碼:
/*** Calls the specified function [block] with `this` value as its receiver and returns `this` value.*/ .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()}} 復制代碼5.also
看一下also函數的源碼:
/*** Calls the specified function [block] with `this` value as its argument and returns `this` value.*/ .internal.InlineOnly 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}} 復制代碼6.let
看一下let函數的源碼:
/*** Calls the specified function [block] with `this` value as its argument and returns its result.*/ .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? = nullTODO()email?.let { email.setText("請輸入郵箱地址")email.setTextColor(getColor(R.color.abc_btn_colored_text_material))} 復制代碼7.takeIf
看一下takeIf函數的源碼:
/*** Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.*/ .internal.InlineOnly 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("郵箱地址不能為空") 復制代碼8.takeUnless
給出takeUnless函數的源碼:
/*** Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does.*/ .internal.InlineOnly 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("郵箱地址不能為空") 復制代碼9.repeat
給出repeat函數的源碼:
/*** Executes the given function [action] specified number of [times].** A zero-based index of current iteration is passed as a parameter to [action].*/ .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這幾個函數區別不是很明顯,有時候使用其中一個函數實現的邏輯,完全也可以用另外一個函數實現,具體使用哪一個,根據個人習慣。需要注意的是:
對這幾個函數的區別做一個對比:
| run | no | no | - |
| T.run | yes | no | it |
| with | no | no | this |
| apply | yes | yes | this |
| also | yes | yes | it |
| let | yes | no | it |
學習資料
轉載于:https://juejin.im/post/5be16ae8f265da61141c12b1
總結
以上是生活随笔為你收集整理的Kotlin中的高阶函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker学习——Dockerfile
- 下一篇: WPF 的 ElementName 在