阅读 608

Kotlin之一文彻底搞懂Standard.kt内置高阶函数

前言

在使用 Kotlin 进行开发时,我们不可避免的需要使用到 Standard.kt 内置的高阶函数:

Standard.kt

对刚刚接触 Kotlin 开发的来说,使用的过程中难免会有些吃力,这里对 Standard.kt 中的标准函数做一些总结与使用归纳。


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 {}
复制代码

分析:

  • 要求传递的是一个代码块,同时返回一个任意类型

说明:但凡函数接收的是一个代码块时,使用的时候一般都建议使用 {} 来包含代码块中的逻辑,只有在一些特殊情况下可以参数 (::fun) 的形式进行简化

例如:

run {
    println(888)
}

val res = run { 2 + 3 }
复制代码

这没什么难度,这里我想要说的是:
但凡涉及到需要传递的代码块参数,都可以省略不传递,对于参数只是一个代码块的时候,可以直接用 ::fun【方法】 的形式传递到 () 中。
啥意思?简单来讲,如果传递单代码块格式是 block: () 这样的,我们可以这么干:

fun runDemo() {
    println("测试run方法")
}

//我们可以这么干
run(::runDemo)
复制代码

也就是说代码块格式为block: ()这种的,用 () 设置的方法必须是不含有参数的,例如上面的 runDemo() 方法就没有参数。

第二种 T.run()
public inline fun <T, R> T.run(block: T.() -> R): R {}
复制代码

分析:

  • 此处是执行一个 T 类型的 run 方法,传递的依然是一个代码块,
  • 只是内部执行的是 T 的内部一个变量 或 方法等,返回的是 一个 R 类型
val str = "hello"
val len = str.run {
    length
}
复制代码

上面例子,一个字符串 str,我们执行 strrun 方法,此时在 run 方法中,我们可以调用 String 类中的一些方法,例如调用 length 返回的是一个 Int 类型结果。

这种在执行一个类的中多个方法的时候,并且要求返回一个结果的时候,使用这个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 等一系列高阶函数,都能快速掌握。

关注下面的标签,发现更多相似文章
评论