Kotlin之一文完全搞懂Standard.kt內置高階函數

前言

在使用 Kotlin 進行開發時,咱們不可避免的須要使用到 Standard.kt 內置的高階函數:app

Standard.kt

對剛剛接觸 Kotlin 開發的來講,使用的過程當中不免會有些吃力,這裏對 Standard.kt 中的標準函數作一些總結與使用概括。less


run() 與 T.run()

run() 方法存在兩種:ide

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 {}
複製代碼

分析:函數

  • 要求傳遞的是一個代碼塊,同時返回一個任意類型

說明:但凡函數接收的是一個代碼塊時,使用的時候通常都建議使用 {} 來包含代碼塊中的邏輯,只有在一些特殊狀況下能夠參數 (::fun) 的形式進行簡化測試

例如:ui

run {
    println(888)
}

val res = run { 2 + 3 }
複製代碼

這沒什麼難度,這裏我想要說的是:
但凡涉及到須要傳遞的代碼塊參數,均可以省略不傳遞,對於參數只是一個代碼塊的時候,能夠直接用 ::fun【方法】 的形式傳遞到 () 中。
啥意思?簡單來說,若是傳遞單代碼塊格式是 block: () 這樣的,咱們能夠這麼幹:this

fun runDemo() {
    println("測試run方法")
}

//咱們能夠這麼幹
run(::runDemo)
複製代碼

也就是說代碼塊格式爲block: ()這種的,用 () 設置的方法必須是不含有參數的,例如上面的 runDemo() 方法就沒有參數。spa

第二種 T.run()
public inline fun <T, R> T.run(block: T.() -> R): R {}
複製代碼

分析:code

  • 此處是執行一個 T 類型的 run 方法,傳遞的依然是一個代碼塊,
  • 只是內部執行的是 T 的內部一個變量 或 方法等,返回的是 一個 R 類型
val str = "hello"
val len = str.run {
    length
}
複製代碼

上面例子,一個字符串 str,咱們執行 strrun 方法,此時在 run 方法中,咱們能夠調用 String 類中的一些方法,例如調用 length 返回的是一個 Int 類型結果。cdn

這種在執行一個類的中多個方法的時候,而且要求返回一個結果的時候,使用這個run方法可以節省不少代碼量。

一樣的,對於方法傳遞的是一個代碼塊的函數而言,若是其傳遞的代碼塊格式是 block: T.() 這種,咱們可使用 ::fun 的形式傳遞到 () 中,只是這個傳遞的方法要求必須含有一個參數傳遞。 說的很繞口,直接看代碼:

val str = "hello"
str.run(::println)

//println函數
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() 方法接收一個類型爲 T 的參數和一個代碼塊
  • 通過處理返回一個 R 類型的結果
  • 這個其實和上面的 T.run() 方法很相似,只是這裏將 T 傳遞到了with() 方法當中
val str = "hello"
val ch = with(str) {
    get(0)
}
println(ch) //打印 h
複製代碼

一樣的,這裏代碼塊格式是 block: T.() 這種,所以根據上面說的規則,咱們一樣能夠寫成下面這樣:

val ch2 = with(str, ::printWith)

fun printWith(str: String): Char? {
    return if (str.isEmpty()) null else str[0]
}
複製代碼

什麼場景下使用 with() 比較合適?下面代碼中就很好的使用了 with() 方法簡化了代碼:

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 {}
複製代碼

分析:

  • 執行一個 T 類型中的方法,變量等,而後返回自身 T
  • 注意參數 block: T.(),但凡看到 block: T.() -> 這種代碼塊,意味着在大括號 {} 中能夠直接調用T內部的 API 而不須要在加上 T. 這種【實際上調用爲 this.this. 一般省略】
val str = "hello"
str.apply { length }    //能夠省略 str.
str.apply { this.length } //能夠這樣

//block: T.()格式代碼塊,所以一樣能夠這麼寫:
str.apply(::println)
複製代碼

實際開發中,一般配合判空 ? 一塊使用,減小 if 判斷,例以下面這樣:

var str: String? = "hello"
//一系列操做後。。。
str?.apply(::println) ?: println("結果爲空")
複製代碼

上面代碼,若是字符串 str 不爲空直接打印出來,若是爲空則打印 結果爲空


T.also()

public inline fun <T> T.also(block: (T) -> Unit): T {}
複製代碼

分析:

  • 執行一個 T 類型中的方法,變量等,而後返回自身 T
  • 這個方法與上面的 apply 方法相似,只是在大括號中執行 T 自身方法的時候,必需要加上 T. 不然沒法調用 T 中的 API,什麼意思呢?看下面代碼:
val str = "hello"
str.also { str.length }  //str.必須加上,不然編譯報錯
str.also { it.length }   //或者用 it.
複製代碼

上面代碼中 {} 中使用了 it 來代替 str,其實咱們還能夠手動指定名稱:

//{}中的s表明的就是str
str.also { s -> s.length }
複製代碼

這就是also與apply的區別所在。

另外,須要注意的是also入參的代碼塊樣式:block: (T),這種樣式跟 block: T.()同樣,可使用 ::fun 的形式傳遞到 () 中,只是這個傳遞的方法要求必須含有一個參數傳遞。
所以咱們能夠這樣操做:

str.also(::println)
複製代碼

T.let()

public inline fun <T, R> T.let(block: (T) -> R): R {}
複製代碼

分析: let 方法與上面的 also 方法及其相似,只是 also 方法返回的結果是自身,而 let 方法是傳遞類型 T 返回另一個類型 R 形式,所以在用法上也很相似:

var str:String? = "hello"
//...一堆邏輯執行後
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
}
複製代碼

分析:

  • 根據傳遞的參數 T 作內部判斷,根據判斷結果返回 null 或者 T 自身
  • 傳遞的是【一元謂詞】代碼塊,像極了 C++ 中的一元謂詞:方法只含有一個參數,而且返回類型是Boolean類型
  • 源碼中,經過傳遞的一元謂詞代碼塊進行判斷,若是是 true 則返回自身,不然返回 null

看下使用代碼:

val str = "helloWorld"
str.takeIf { str.contains("hello") }?.run(::println)
複製代碼

上面代碼{}中判斷字符串是否包含 "hello",是則返回本身,不是則返回 null,所以可使用?來判斷,若是不爲null,可使用前面說的 run() 方法進行簡單打印操做。 一樣的,由於接收的代碼塊是一個一元謂詞形式,所以,若是想要使用 (::fun) 方式來替代 {},則對應的函數方法必須知足兩個條件:

  • 返回值類型是 Boolean 類型
  • 方法必須含有一個參數
    所以能夠寫成下面這種:
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
}
複製代碼

分析:這個方法跟 takeIf() 方法相似,只是內部判斷爲false的時候返回自身T ,而 true 的時候返回 null,所以不過多說明,使用參考 takeIf() 方法。


repeat()

public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }

    for (index in 0 until times) {
        action(index)
    }
}
複製代碼

分析:repeat 方法包含兩個參數:

  • 第一個參數int類型,重複次數,
  • 第二個參數,表示要重複執行的對象
  • 該方法每次執行的時候都將執行的次數傳遞給要被重複執行的模塊,至於重複執行模塊是否須要該值,須要根據業務實際需求考慮,例如:
//打印從0 到 100 的值,次數用到了內部的index
 repeat(100) {
    print(it)
}

//有好比,單純的打印helloworld 100 次,就沒有用到index值
repeat(100){
    println("helloworld")
}
複製代碼

注意看傳遞的代碼塊格式:action: (Int),這就說明了要想使用(::fun)形式簡化{}部分,須要代碼塊知足一個條件:

  • 方法傳遞的參數有且只有一個 Int 類型或者 Any 的參數
repeat(100, ::print)
repeat(100, ::printRepeat)


fun printRepeat(int: Int) {
    print(int)
}
複製代碼

總結時刻

不論是 Kotlin 中內置的高階函數,仍是咱們自定義的,其傳入的代碼塊樣式,無非如下幾種:

  • block: () -> Tblock: () -> 具體類型
    這種在使用 (::fun) 形式簡化時,要求傳入的方法必須是無參數的,返回值類型若是是T則可爲任意類型,不然返回的類型必需要跟這個代碼塊返回類型一致
  • block: T.() -> Rblock: T.() -> 具體類型
    這種在使用 (::fun) 形式簡化時,要求傳入的方法必須包含一個T類型的參數,返回值類型若是是R則可爲任意類型,不然返回的類型必需要跟這個代碼塊返回類型一致。例如 withapply 這兩個方法
  • block: (T) -> Rblock: (T) -> 具體類型
    這種在使用 (::fun) 形式簡化時,要求傳入的方法必須包含一個T類型的參數,返回值類型若是是R則可爲任意類型,不然返回的類型必需要跟這個代碼塊返回類型一致。例如 lettakeIf 這兩個方法

只有搞清楚上面這三種代碼塊格式及其用法,對應的其餘的一些例如 Strings.kt 中的 filtertakeWhileflatMap 等一系列高階函數,都能快速掌握。

相關文章
相關標籤/搜索