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 }

这没什么难度,这里我想要说的是:

<font color=red>但凡涉及到需要传递的代码块参数,都可以省略不传递,对于参数只是一个代码块的时候,可以直接用 ::fun【方法】 的形式传递到 () 中。</font>

啥意思?简单来讲,如果传递单代码块格式是 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 等一系列高阶函数,都能快速掌握。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,034评论 1 32
  • 本文是在学习和使用kotlin时的一些总结与体会,一些代码示例来自于网络或Kotlin官方文档,持续更新... 对...
    竹尘居士阅读 3,208评论 0 8
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,517评论 0 38
  • 1. 基础定义 1.1 什么是高阶函数 按照定义,高阶函数就是以另外一个函数作为参数或者返回值的函数。在Kotli...
    0xCAFEBABE51阅读 3,807评论 0 13
  • 大四了,小胖和大姐还没回寝室,还有13天,我的设计任务就必须完成了。 今天一天有2/3的时间在床上度过,昨天睡的太...
    鱼缸兜不住的鱼阅读 166评论 0 0