前言
在使用 Kotlin
進(jìn)行開(kāi)發(fā)時(shí),我們不可避免的需要使用到 Standard.kt
內(nèi)置的高階函數(shù):
對(duì)剛剛接觸 Kotlin
開(kāi)發(fā)的來(lái)說(shuō)筐带,使用的過(guò)程中難免會(huì)有些吃力,這里對(duì) Standard.kt
中的標(biāo)準(zhǔn)函數(shù)做一些總結(jié)與使用歸納缤灵。
run() 與 T.run()
run()
方法存在兩種:
public inline fun <R> run(block: () -> R): R {}
public inline fun <T, R> T.run(block: T.() -> R): R {}
第二種 run()
public inline fun <R> run(block: () -> R): R {}
分析:
- 要求傳遞的是一個(gè)代碼塊伦籍,同時(shí)返回一個(gè)任意類(lèi)型
說(shuō)明:但凡函數(shù)接收的是一個(gè)代碼塊時(shí),使用的時(shí)候一般都建議使用 {}
來(lái)包含代碼塊中的邏輯腮出,只有在一些特殊情況下可以參數(shù) (::fun)
的形式進(jìn)行簡(jiǎn)化
例如:
run {
println(888)
}
val res = run { 2 + 3 }
這沒(méi)什么難度帖鸦,這里我想要說(shuō)的是:
<font color=red>但凡涉及到需要傳遞的代碼塊參數(shù),都可以省略不傳遞胚嘲,對(duì)于參數(shù)只是一個(gè)代碼塊的時(shí)候作儿,可以直接用 ::fun
【方法】 的形式傳遞到 ()
中。</font>
啥意思馋劈?簡(jiǎn)單來(lái)講攻锰,如果傳遞單代碼塊格式是 block: ()
這樣的,我們可以這么干:
fun runDemo() {
println("測(cè)試run方法")
}
//我們可以這么干
run(::runDemo)
也就是說(shuō)代碼塊格式為block: ()
這種的妓雾,用 ()
設(shè)置的方法必須是不含有參數(shù)的娶吞,例如上面的 runDemo()
方法就沒(méi)有參數(shù)。
第二種 T.run()
public inline fun <T, R> T.run(block: T.() -> R): R {}
分析:
- 此處是執(zhí)行一個(gè)
T
類(lèi)型的run
方法君珠,傳遞的依然是一個(gè)代碼塊寝志, - 只是內(nèi)部執(zhí)行的是
T
的內(nèi)部一個(gè)變量 或 方法等,返回的是 一個(gè)R
類(lèi)型
val str = "hello"
val len = str.run {
length
}
上面例子策添,一個(gè)字符串 str
材部,我們執(zhí)行 str
的 run
方法,此時(shí)在 run
方法中唯竹,我們可以調(diào)用 String
類(lèi)中的一些方法乐导,例如調(diào)用 length
返回的是一個(gè) Int
類(lèi)型結(jié)果。
這種在執(zhí)行一個(gè)類(lèi)的中多個(gè)方法的時(shí)候浸颓,并且要求返回一個(gè)結(jié)果的時(shí)候物臂,使用這個(gè)run方法能夠節(jié)省很多代碼量。
同樣的产上,對(duì)于方法傳遞的是一個(gè)代碼塊的函數(shù)而言棵磷,如果其傳遞的代碼塊格式是 block: T.()
這種,我們可以使用 ::fun
的形式傳遞到 ()
中晋涣,只是這個(gè)傳遞的方法要求必須含有一個(gè)參數(shù)傳遞仪媒。 說(shuō)的很繞口,直接看代碼:
val str = "hello"
str.run(::println)
//println函數(shù)
public actual inline fun println(message: Any?) {
System.out.println(message)
}
with()
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {}
分析:
-
with()
方法接收一個(gè)類(lèi)型為T
的參數(shù)和一個(gè)代碼塊 - 經(jīng)過(guò)處理返回一個(gè)
R
類(lèi)型的結(jié)果 - 這個(gè)其實(shí)和上面的
T.run()
方法很類(lèi)似谢鹊,只是這里將T
傳遞到了with()
方法當(dāng)中
val str = "hello"
val ch = with(str) {
get(0)
}
println(ch) //打印 h
同樣的算吩,這里代碼塊格式是 block: T.()
這種留凭,因此根據(jù)上面說(shuō)的規(guī)則,我們同樣可以寫(xiě)成下面這樣:
val ch2 = with(str, ::printWith)
fun printWith(str: String): Char? {
return if (str.isEmpty()) null else str[0]
}
什么場(chǎng)景下使用 with()
比較合適偎巢?下面代碼中就很好的使用了 with()
方法簡(jiǎn)化了代碼:
class Preference<T>(val context: Context, val name: String, val default: T, val prefName: String = "default") :
ReadWriteProperty<Any?, T> {
private val prefs by lazy {
context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
}
//注解消除警告
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (default) {
is String -> prefs.getString(name, default)
is Int -> prefs.getInt(name, default)
is Long -> prefs.getLong(name, default)
is Float -> prefs.getFloat(name, default)
else -> throw IllegalStateException("Unsupported data.")
} as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
with(prefs.edit()) {
when (value) {
is String -> putString(name, value)
is Int -> putInt(name, value)
is Long -> putLong(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalStateException("Unsupported data.")
}
}.apply()
}
}
T.apply()
public inline fun <T> T.apply(block: T.() -> Unit): T {}
分析:
- 執(zhí)行一個(gè)
T
類(lèi)型中的方法蔼夜,變量等,然后返回自身T
- 注意參數(shù)
block: T.()
压昼,但凡看到block: T.() ->
這種代碼塊求冷,意味著在大括號(hào){}
中可以直接調(diào)用T內(nèi)部的API
而不需要在加上T.
這種【實(shí)際上調(diào)用為this.
,this.
通常省略】
val str = "hello"
str.apply { length } //可以省略 str.
str.apply { this.length } //可以這樣
//block: T.()格式代碼塊巢音,因此同樣可以這么寫(xiě):
str.apply(::println)
實(shí)際開(kāi)發(fā)中遵倦,通常配合判空 ?
一塊使用,減少 if
判斷官撼,例如下面這樣:
var str: String? = "hello"
//一系列操作后梧躺。。傲绣。
str?.apply(::println) ?: println("結(jié)果為空")
上面代碼掠哥,如果字符串 str
不為空直接打印出來(lái),如果為空則打印 結(jié)果為空
T.also()
public inline fun <T> T.also(block: (T) -> Unit): T {}
分析:
- 執(zhí)行一個(gè)
T
類(lèi)型中的方法秃诵,變量等续搀,然后返回自身T
- 這個(gè)方法與上面的
apply
方法類(lèi)似,只是在大括號(hào)中執(zhí)行T
自身方法的時(shí)候菠净,必須要加上 T. 否則無(wú)法調(diào)用T
中的API
,什么意思呢禁舷?看下面代碼:
val str = "hello"
str.also { str.length } //str.必須加上,否則編譯報(bào)錯(cuò)
str.also { it.length } //或者用 it.
上面代碼中 {}
中使用了 it
來(lái)代替 str
毅往,其實(shí)我們還可以手動(dòng)指定名稱(chēng):
//{}中的s代表的就是str
str.also { s -> s.length }
這就是also與apply的區(qū)別所在牵咙。
另外,需要注意的是also入?yún)⒌拇a塊樣式:block: (T)
攀唯,這種樣式跟 block: T.()
一樣洁桌,可以使用 ::fun
的形式傳遞到 ()
中,只是這個(gè)傳遞的方法要求必須含有一個(gè)參數(shù)傳遞侯嘀。
因此我們可以這樣操作:
str.also(::println)
T.let()
public inline fun <T, R> T.let(block: (T) -> R): R {}
分析:
let
方法與上面的 also
方法及其類(lèi)似另凌,只是 also
方法返回的結(jié)果是自身,而 let
方法是傳遞類(lèi)型 T
返回另外一個(gè)類(lèi)型 R
形式戒幔,因此在用法上也很類(lèi)似:
var str:String? = "hello"
//...一堆邏輯執(zhí)行后
val len = str?.let { it.length }
str.let(::println)
T.takeIf()
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (predicate(this)) this else null
}
分析:
- 根據(jù)傳遞的參數(shù)
T
做內(nèi)部判斷吠谢,根據(jù)判斷結(jié)果返回null
或者T
自身 - 傳遞的是【一元謂詞】代碼塊,像極了
C++
中的一元謂詞:方法只含有一個(gè)參數(shù)诗茎,并且返回類(lèi)型是Boolean類(lèi)型 - 源碼中囊卜,通過(guò)傳遞的一元謂詞代碼塊進(jìn)行判斷,如果是
true
則返回自身错沃,否則返回null
看下使用代碼:
val str = "helloWorld"
str.takeIf { str.contains("hello") }?.run(::println)
上面代碼{}中判斷字符串是否包含 "hello"
栅组,是則返回自己,不是則返回 null枢析,因此可以使用?來(lái)判斷玉掸,如果不為null
,可以使用前面說(shuō)的 run()
方法進(jìn)行簡(jiǎn)單打印操作醒叁。
同樣的司浪,因?yàn)榻邮盏拇a塊是一個(gè)一元謂詞形式,因此把沼,如果想要使用 (::fun)
方式來(lái)替代 {}
啊易,則對(duì)應(yīng)的函數(shù)方法必須滿(mǎn)足兩個(gè)條件:
- 返回值類(lèi)型是
Boolean
類(lèi)型 - 方法必須含有一個(gè)參數(shù)
因此可以寫(xiě)成下面這種:
val str = "helloWorld"
str.takeIf(::printTakeIf)?.run(::println)
fun printTakeIf(str: String): Boolean {
return str.contains("hello")
}
T.takeUnless()
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (!predicate(this)) this else null
}
分析:這個(gè)方法跟 takeIf()
方法類(lèi)似,只是內(nèi)部判斷為false的時(shí)候返回自身T 饮睬,而 true
的時(shí)候返回 null
租谈,因此不過(guò)多說(shuō)明,使用參考 takeIf()
方法捆愁。
repeat()
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
分析:repeat
方法包含兩個(gè)參數(shù):
- 第一個(gè)參數(shù)int類(lèi)型割去,重復(fù)次數(shù),
- 第二個(gè)參數(shù),表示要重復(fù)執(zhí)行的對(duì)象
- 該方法每次執(zhí)行的時(shí)候都將執(zhí)行的次數(shù)傳遞給要被重復(fù)執(zhí)行的模塊昼丑,至于重復(fù)執(zhí)行模塊是否需要該值呻逆,需要根據(jù)業(yè)務(wù)實(shí)際需求考慮,例如:
//打印從0 到 100 的值菩帝,次數(shù)用到了內(nèi)部的index
repeat(100) {
print(it)
}
//有比如咖城,單純的打印helloworld 100 次,就沒(méi)有用到index值
repeat(100){
println("helloworld")
}
注意看傳遞的代碼塊格式:action: (Int)
呼奢,這就說(shuō)明了要想使用(::fun)形式簡(jiǎn)化{}部分宜雀,需要代碼塊滿(mǎn)足一個(gè)條件:
- 方法傳遞的參數(shù)有且只有一個(gè)
Int
類(lèi)型或者Any
的參數(shù)
repeat(100, ::print)
repeat(100, ::printRepeat)
fun printRepeat(int: Int) {
print(int)
}
總結(jié)時(shí)刻
不管是 Kotlin
中內(nèi)置的高階函數(shù),還是我們自定義的控妻,其傳入的代碼塊樣式州袒,無(wú)非以下幾種:
-
block: () -> T
和block: () -> 具體類(lèi)型
這種在使用(::fun)
形式簡(jiǎn)化時(shí),要求傳入的方法必須是無(wú)參數(shù)的弓候,返回值類(lèi)型如果是T則可為任意類(lèi)型郎哭,否則返回的類(lèi)型必須要跟這個(gè)代碼塊返回類(lèi)型一致 -
block: T.() -> R
和block: T.() -> 具體類(lèi)型
這種在使用(::fun)
形式簡(jiǎn)化時(shí),要求傳入的方法必須包含一個(gè)T類(lèi)型的參數(shù)菇存,返回值類(lèi)型如果是R則可為任意類(lèi)型夸研,否則返回的類(lèi)型必須要跟這個(gè)代碼塊返回類(lèi)型一致。例如with
和apply
這兩個(gè)方法 -
block: (T) -> R
和block: (T) -> 具體類(lèi)型
這種在使用(::fun)
形式簡(jiǎn)化時(shí)依鸥,要求傳入的方法必須包含一個(gè)T類(lèi)型的參數(shù)亥至,返回值類(lèi)型如果是R則可為任意類(lèi)型,否則返回的類(lèi)型必須要跟這個(gè)代碼塊返回類(lèi)型一致。例如let
和takeIf
這兩個(gè)方法
只有搞清楚上面這三種代碼塊格式及其用法姐扮,對(duì)應(yīng)的其他的一些例如 Strings.kt
中的 filter
絮供、takeWhile
、flatMap
等一系列高階函數(shù)茶敏,都能快速掌握壤靶。