Kotlin之一文徹底搞懂Standard.kt內(nèi)置高階函數(shù)

前言

在使用 Kotlin 進(jìn)行開(kāi)發(fā)時(shí),我們不可避免的需要使用到 Standard.kt 內(nèi)置的高階函數(shù):

Standard.kt

對(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í)行 strrun 方法,此時(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: () -> Tblock: () -> 具體類(lèi)型

    這種在使用 (::fun) 形式簡(jiǎn)化時(shí),要求傳入的方法必須是無(wú)參數(shù)的弓候,返回值類(lèi)型如果是T則可為任意類(lèi)型郎哭,否則返回的類(lèi)型必須要跟這個(gè)代碼塊返回類(lèi)型一致
  • block: T.() -> Rblock: T.() -> 具體類(lèi)型

    這種在使用 (::fun) 形式簡(jiǎn)化時(shí),要求傳入的方法必須包含一個(gè)T類(lèi)型的參數(shù)菇存,返回值類(lèi)型如果是R則可為任意類(lèi)型夸研,否則返回的類(lèi)型必須要跟這個(gè)代碼塊返回類(lèi)型一致。例如 withapply 這兩個(gè)方法
  • block: (T) -> Rblock: (T) -> 具體類(lèi)型

    這種在使用 (::fun) 形式簡(jiǎn)化時(shí)依鸥,要求傳入的方法必須包含一個(gè)T類(lèi)型的參數(shù)亥至,返回值類(lèi)型如果是R則可為任意類(lèi)型,否則返回的類(lèi)型必須要跟這個(gè)代碼塊返回類(lèi)型一致。例如 lettakeIf 這兩個(gè)方法

只有搞清楚上面這三種代碼塊格式及其用法姐扮,對(duì)應(yīng)的其他的一些例如 Strings.kt 中的 filter絮供、takeWhileflatMap 等一系列高階函數(shù)茶敏,都能快速掌握壤靶。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惊搏,隨后出現(xiàn)的幾起案子贮乳,更是在濱河造成了極大的恐慌,老刑警劉巖恬惯,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件向拆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡酪耳,警方通過(guò)查閱死者的電腦和手機(jī)浓恳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)葡兑,“玉大人奖蔓,你說(shuō)我怎么就攤上這事《锏蹋” “怎么了吆鹤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)洲守。 經(jīng)常有香客問(wèn)我疑务,道長(zhǎng),這世上最難降的妖魔是什么梗醇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任知允,我火速辦了婚禮,結(jié)果婚禮上叙谨,老公的妹妹穿的比我還像新娘温鸽。我一直安慰自己,他們只是感情好手负,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布涤垫。 她就那樣靜靜地躺著,像睡著了一般竟终。 火紅的嫁衣襯著肌膚如雪蝠猬。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天统捶,我揣著相機(jī)與錄音榆芦,去河邊找鬼柄粹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛匆绣,可吹牛的內(nèi)容都是我干的驻右。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼犬绒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼旺入!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起凯力,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎礼华,沒(méi)想到半個(gè)月后咐鹤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡圣絮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年祈惶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扮匠。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捧请,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出棒搜,到底是詐尸還是另有隱情疹蛉,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布力麸,位于F島的核電站可款,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏克蚂。R本人自食惡果不足惜闺鲸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望埃叭。 院中可真熱鬧摸恍,春花似錦、人聲如沸赤屋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)益缎。三九已至谜慌,卻和暖如春扁达,著一層夾襖步出監(jiān)牢的瞬間委煤,已是汗流浹背淮捆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留熬荆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓炬太,卻偏偏與公主長(zhǎng)得像拆讯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晴竞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 本文是在學(xué)習(xí)和使用kotlin時(shí)的一些總結(jié)與體會(huì)蛙卤,一些代碼示例來(lái)自于網(wǎng)絡(luò)或Kotlin官方文檔,持續(xù)更新... 對(duì)...
    竹塵居士閱讀 3,269評(píng)論 0 8
  • Lua 5.1 參考手冊(cè) by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,743評(píng)論 0 38
  • 1. 基礎(chǔ)定義 1.1 什么是高階函數(shù) 按照定義噩死,高階函數(shù)就是以另外一個(gè)函數(shù)作為參數(shù)或者返回值的函數(shù)颤难。在Kotli...
    0xCAFEBABE51閱讀 3,873評(píng)論 0 13
  • 大四了,小胖和大姐還沒(méi)回寢室已维,還有13天行嗤,我的設(shè)計(jì)任務(wù)就必須完成了。 今天一天有2/3的時(shí)間在床上度過(guò)垛耳,昨天睡的太...
    魚(yú)缸兜不住的魚(yú)閱讀 168評(píng)論 0 0