Kotlin 中的 run、let、with、apply、also、takeIf、takeUnless 语法糖使用和原理分析
這些Kotlin的語法糖函數經常用,但也很容易搞混,所以轉載一下,若混了可以回來再看
轉載自公眾號:紙上淺談
?
正文:
在 Kotlin 有一些可以簡化代碼的語法糖,比如 run、let、with、apply、also、takeIf、takeUnless ?等。
再不明白這些語法糖的情況下去看 Kotlin 代碼就會一臉懵逼,可當明白之后就會覺得原來可以這樣簡化。
帶接收者的函數字面值
使用這些語法糖之前回顧一下 Kotlin 的函數式編程,在分析 Kotlin 使用 Anko 構建布局 文章中有提到?帶接收者的函數字面值。
它的形式是這樣的:
1//?定義一個類 2class?ReceiveObject? 3//?定義一個函數 4fun?exec(invoke:?ReceiveObject.()->?Int){}在 Kotlin 中,函數也可以當做變量傳參,例如:
1fun?funAsArg(args:()->Int){} 2//?調用 3funAsArg?{?2?}args?是變量名,它的類型就是函數,函數形式在變量名后面約定:()->Int,函數沒有參數,但是會返回一個 Int 類型的值。
而帶接收者的函數字面值,就是在作為傳入參數的函數變量的具體函數形式的參數前面多了接收者對象,簡單說就是在?()前面多了一個點和一個對象,成了如下的形式:
1fun?exec(invoke:?ReceiveObject.()->?Int){}就是這多了的一個點和一個對象,讓它有了不一樣的功能。
簡單的說,invoke 變量是一個函數作為變量,需要傳遞一個具體函數實現作為形參給 invoke,那么在具體函數實現里面就可以調用接收者對象 ReceiveObject 的相關方法,如下:
1????//?接收者對象,有個?show?方法 2????class?ReceiveObject{ 3????????fun?show(){ 4????????????println("call") 5????????} 6????} 7????//?具體函數實現 8????val?invoke:?ReceiveObject.()?->?Int?=?{ 9????????this.show()?//?用?this?指代?接收者對象?ReceiveObject 10????????2 11????} 12????exec(invoke)如上,在 invoke 方法里面使用?this?指代 ReceiveObject 對象,可以調用它的方法。
而 invoke 變量是作為參數傳遞給 exec 函數的,如果 exec 函數為空,那么 inkoke 具體實現的 show 方法也不會被調用的,在 exec 中調用 invoke 的方法如下:
1fun?exec(invoke:?ReceiveObject.()?->?Int){ 2????val?receObj?=?ReceiveObject() 3????//?兩種調用形式 4????//?類似于?ReceiceObject?拓展函數一樣的調用 5????receObj.invoke()? 6????//?把?ReceiceObject?作為參數傳遞給?invoke?調用 7????invoke(receObj) 8}在 exec 的具體調用中,我們需要構造一個 ReceiveObject 對象實例,不然怎么去調用它的 show 方法呢。
在上面的例子中,還需要構造一個指定的接收者對象實例才能完成 invoke 的調用,而 Kotlin 的語法糖中還有一種叫做?拓展函數。
拓展函數
拓展函數相當于給某個類添加函數,但這個函數并不屬于這個類的函數,和 static 方法是兩碼事。
1????fun?Context.showToast(msg:?String)?{ 2????????Toast.makeText(this,?msg,?Toast.LENGTH_SHORT).show() 3????}在拓展函數中,使用 this 指代被拓展的類實例,上面代碼中 this 指代就是 Context 。
有了 拓展函數和帶接收者的函數字面值,就可以實現文章標題提到的那些語法糖了。
例如,針對 ReceiveObject 對象添加它的拓展函數,拓展函數的參數又是一個函數,函數是帶接收者的函數字面值,這個接收者對象就是 ReceiveObject 對象它本身,這樣調用 invoke 方法就不用再構造 ReceiveObject 對象了。
1fun?ReceiveObject.exec(invoke:?ReceiveObject.()?->?Int){ 2????invoke() 3}語法糖
下面介紹的語法糖都是位于 Kotlin Standard.kt 文件中的。
run 語法糖
run 的語法糖有兩種:
1public?inline?fun?<R>?run(block:?()?->?R):?R?{ 2????contract?{ 3????????callsInPlace(block,?InvocationKind.EXACTLY_ONCE) 4????} 5????return?block() 6}這種語法糖傳遞的參數就僅僅是一個函數,不是帶接收者對象的函數字面值,它的返回結果就是 block 函數調用后的結果。
調用示例:
1????var?result?=?kotlin.run?{? 2????????????"value" 3????????}相對于給 arg 變量賦值為 value 字符串。
run 的另一種語法糖:
1public?inline?fun?<T,?R>?T.run(block:?T.()?->?R):?R?{ 2????contract?{ 3????????callsInPlace(block,?InvocationKind.EXACTLY_ONCE) 4????} 5????return?block() 6}首先,這個語法糖是一個拓展函數,而且用到了泛型?<T,R>,T 類型的拓展函數,返回的是 R 類型,T 和 R 可以相同。
其次,傳遞的參數是帶接收者對象的函數字面值,也就是說可以在 block 函數里面調用 T 的相關方法,通過 this 來指代 T ,在 run 方法內部就是調用了 block 方法,返回 block 函數調用后的結果。
調用示例:
1????????????val?result?=?"a".run?{ 2????????????????this.plus("b") 3????????????}Contracts DSL
在 run 的語法糖里面還出現了如下一段代碼:
1?contract?{ 2??????callsInPlace(block,?InvocationKind.EXACTLY_ONCE) 3???}Google 了一番之后
https://discuss.kotlinlang.org/t/status-of-kotlin-internal-contracts/6392/2
https://stackoverflow.com/questions/49729037/how-does-kotlin-internal-contracts-contractbuilderktcontract-work-in-kotlin
https://aisia.moe/2018/03/25/kotlin-contracts-dsl/
得出原來這是 Kotlin 1.2.x 版本中出現的,但實際并沒有用,是 Kotlin 后續發展用來解決如下代碼問題的:
1if?(!x.isNullOrEmpty())?{? 2??//?we?know?that?'x?!=?null'?here? 3??println(x.length) 4}假設 x 是可以為 null 的,經過 isNullOrEmpty 函數判斷之后,再執行 println 函數,那么它肯定就不是 null 了,就不需要再加兩個?!!?來表示 x 不為 null 了,而現在的情況是要添加?!!?。
從 Google 來的信息得知, contract 這段代碼就是為了這樣的問題的。
由于語法糖都有那樣一段代碼,所以就先把它們去掉了。
let 語法糖
1public?inline?fun?<T,?R>?T.let(block:?(T)?->?R):?R?{ 2????return?block(this) 3}let 語法糖傳遞的參數是一個函數,不是帶接收者的函數字面值,但 block 函數的參數就是 T 類型,所以可以在 block 里面調用 T 類型的方法,但不能通過 this 來指代 T 了,通過 it 來指代 T 類型。
調用示例:
1????????????val?result?=?"a".let?{ 2????????????????it.plus("b") 3????????????}with 語法糖
1public?inline?fun?<T,?R>?with(receiver:?T,?block:?T.()?->?R):?R?{ 2????return?receiver.block() 3}with 語法糖不再是一個拓展函數了,而是需要在語法糖的第一個參數里面傳入接收者對象的實例,第二個參數就是帶接收者的函數字面值實例,返回的也是 block 調用的結果,這一點和 run 語法糖類似。
調用示例:
1????????????val?result?=?with("a")?{ 2????????????????this.plus("b") 3????????????}apply 語法糖
1public?inline?fun?<T>?T.apply(block:?T.()?->?Unit):?T?{ 2????block() 3????return?this 4}apply 語法糖和 run 語法糖都類似,只不過它返回的不是 block 函數調用的結果,而是返回調用者本身,返回 T 類型。
also 語法糖
1public?inline?fun?<T>?T.also(block:?(T)?->?Unit):?T?{ 2????block(this) 3????return?this 4}also 語法糖和 let 語法糖有點類似,只不過返回的結果不是 block 調用結果,而是返回它本身,返回 T 類型。
調用示例:
1????????????var?result?=?"a".also?{ 2????????????????it.plus("b") 3????????????}takeIf 語法糖
1public?inline?fun?<T>?T.takeIf(predicate:?(T)?->?Boolean):?T??{ 2????return?if?(predicate(this))?this?else?null 3}takeIf 語法糖會調用 predicate 函數進行判斷,如果為 true 就返回它本身,否則返回 null 。
takeUnless 語法糖
1public?inline?fun?<T>?T.takeUnless(predicate:?(T)?->?Boolean):?T??{ 2????return?if?(!predicate(this))?this?else?null 3}takeUnless 和 takeIf 正好相反,如果 predicate 返回 false 就返回它本身,否則返回 null 。
總結
這么多的語法糖,其實他們的原理都是類似的,共同點在于都是有返回值的,而區別就在于對原有的值進行了哪些操作,然后如何返回最終的值。
最后,光是了解他們的原理和調用情況還是不夠的,再不影響代碼閱讀的情況下要把它們引入到我們的代碼中去,靈活地使用它們。
總結
以上是生活随笔為你收集整理的Kotlin 中的 run、let、with、apply、also、takeIf、takeUnless 语法糖使用和原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (三) LtRecyclerView v
- 下一篇: 防止网络请求(或其他回调)引用,从而造成