Kotlin之一文完全搞懂Standard.kt内置高阶函数

前言

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

Standard.kt

对刚刚接触 Kotlin 开发的来讲,使用的过程当中不免会有些吃力,这里对 Standard.kt 中的标准函数作一些总结与使用概括。app


run() 与 T.run()

run() 方法存在两种:less

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

分析:ide

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

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

例如:测试

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 类型结果。orm

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

相关文章
相关标签/搜索