Kotlin的Standard Library提供了幾種拓展函數(extension function),有的可以更優雅處理可空變量問題。
相信在學習 Kotlin 時,一定都會注意到它有許多let
apply
run
等這些 extension function,它們看起來大同小異,用起來似乎也經常可以相互替換,稍做修改就讓程式依照正確的邏輯執行。
function | identifier | return |
---|---|---|
Let | it | 最後一行 |
run | This | 最後一行 |
also | it | this |
apply | this | this |
常用
T.let
T是什麼?
在Kotlin extension function中需要接在一個變數後面才行。像是 someVariable.run { /* do something */ }
,包含 T.run 下面四個 let also apply 都屬於這種 extension function。因爲 run有兩種用法,這裏爲了避免混淆而將 T 寫出來。
T.let 默認當前這個對象作爲閉包的it參數,返回值是函數裏面最後一行,或者指定return@let
private var name: String? = null
@JvmStatic
fun main(args: Array<String>?) {
name = let {
return@let "wazing"
}
name?.let {
println("my name is $it")
}
}
結果: my name is wazing
name.let{}
如果name爲null,let{}
中的程式碼還是會被執行,只不過此時it
爲null
;name?.let{}
如果 name爲null,let{}
中的程式碼不會執行;T
在 scope 內則是用it
來存取而不是this
。也可以依照需求改成其他的名字,增加可讀性;T.let{}
會作爲下一個擴展函數或者回傳給之前的變數。
Run
@JvmStatic
fun main(args: Array<String>?) {
val name: String = run {
val sb = StringBuilder()
return@run sb.let { sb ->
sb.append("wa")
}.apply {
append("zing")
}.run {
return@run toString()
}
}
println("my name is $name")
}
結果: my name is wazing
同樣如果傳進來的變數是空值,T.run{}
內的程式碼就根本不會執行了。除此之外, T.run
和 run
的特性完全一樣。可以將最後一行的結果回傳,或是作爲下個extension function。
T
在T.run{}
與T.let
的scope內相反,T.run{}
是用this
而不是 it
。
also
可以寫作T.also
與上面的run
與let
的不同之處在於: run
與let
會將最後一行傳給下個extension function或是回傳;also
和apply
則是將「自己(this)」
回傳或傳入下個extension function。
@JvmStatic
fun main(args: Array<String>?) {
val fruits: ArrayList<String> = ArrayList<String>().also {
it.add("蘋果🍎")
}.also {
it.add("梨子🍐")
it.add("菠蘿🍍")
}.apply {
add("西瓜🍉")
}
println("$fruits")
}
結果: [蘋果🍎, 梨子🍐, 菠蘿🍍, 西瓜🍉]
apply
可以寫作T.apply
apply
與also相似,不同的地方是
apply在 scope內
T的存取方式是
this,其他都與
also`一樣。
@JvmStatic
fun main(args: Array<String>?) {
val list: ArrayList<String> = ArrayList<String>().apply {
add("Android")
add("iOS")
add("HongmengOS")
}
println(list.toString())
}
結果: [Android, iOS, HongmengOS]
with
with
一般常常作爲初始化時使用,with(T)
之中的傳入值可以以this
在scope
中取用;
@JvmStatic
fun main(args: Array<String>?) {
with(ArrayList<String>()){
this.add("🍎")
this.add("🍌")
add("🍅")
add("🌰")
remove("🍌")
println("$size")
}
}
結果: 3
但很多使用狀況變數可能是可爲空的變數,如此一來with
的scope
中就必須要宣告 ?
或!!
來取用該物件的方法 (Method)。
@JvmStatic
fun main(args: Array<String>?) {
var list: ArrayList<String>? = null
with(list) {
this?.add("🍎")
this?.add("🍌")
this?.add("🍅")
this?.add("🌰")
this?.remove("🍌")
println("${this?.size}")
}
}
結果: null
repeat
以下爲爲源碼,Standard.kt
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
repeat函數是一個單獨的函數,循環執行[times]次[action]中內容。
takeIf
@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
}
滿足[predicate]中條件,則返回當前值[T],否則返回null,[predicate]的返回值Boolean類型
@JvmStatic
fun main(args: Array<String>?) {
val name = 1.takeIf { it == 1 }
println(name)
}
結果: 1
takeUnless
@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
}
與takeIf
相反,滿足[predicate]中條件,則返回null,否則返回當前值[T],[predicate]的返回值Boolean類型
@JvmStatic
fun main(args: Array<String>?) {
val name = 1.takeUnless { it == 1 }
println(name)
}
結果: null
結尾
Standard Library Function 其實可以互相替換,選擇合適的場景使用即可。