Kotlin Standard.kt 内置函数使用

在 Kotlin 源码的 Standard.kt 文件中提供了一些很好用的内置高阶函数,可以帮助我们写出更优雅的 Kotlin 代码,提高生产力。为了能学习这些高阶函数,有必要先对高阶函数、Lambda表达式有所了解。

接下来我们逐个学习,其中 let、also、with、run、apply 这几个函数的功能很相似,需要我们重点注意,按需使用。

一、 let

let 函数的声明如下:

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

可以看出 let 是一个作用域函数,需要通过一个对象来调用,参数是函数类型,同时 let 函数的返回值类型也是该函数的返回值类型。由于我们一般会用 Lambda 表达式作为函数类型参数的值,那么 let 函数的返回值就是 Lambda 表达式的返回值,以下内容都会采用类似的说法,这一点需要注意。

一个典型的使用场景就是创建一个目标 Activity、Fragment 并接收参数时,可以考虑使用 let 函数,例如在 Fragment 中接收参数时:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

arguments不为空时,Lambda 表达式内it就代替arguments对象来访问其方法。

二、also

also 函数的声明如下:

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

also 函数从 Kotlin1.1开始支持,和 let 函数的声明比较一下,其实是很类似的,唯一的区别就是返回值不同,前边我已经知道 let 函数的返回值可以是 Lambda 表达式 的返回值,而 also 函数的返回值是调用 also 函数的对象:

fun main(args: Array<String>) {
    val let = "kotlin".let {
        it.toUpperCase()
    }

    val also = "kotlin".also {
        it.toUpperCase()
    }

    println("let的返回值:$let")
    println("also的返回值:$also")
}
// 输出
let的返回值:KOTLIN
also的返回值:kotlin

所以除了返回值的差别外,also 函数适合 let 函数的任何使用场景,另外 also 函数更适合链式操作一个对象的属性、方法,并返回该对象的场景:

data class User(var name: String = "", var age: Int = 0, var sex: String = "") {
    override fun toString(): String {
        return "name:$name,age:$age,sex:$sex"
    }
}

fun main(args: Array<String>) {
    val user2 = User().also {
        it.name = "Tom"
        it.age = 18
        it.sex = "male"
    }
    println(user2.toString())
}
// 输出
name:Tom,age:18,sex:male

三、with

with 函数的声明如下:

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

with 函数需要两个参数,需要操作的对象和一个函数类型的参数(一般是 Lambda 表达式),在 Lambda 表达式中可以用this指代要操作的对象或者省略 this 也行,返回值就是 Lambda 表达式的返回值,虽然这个返回值一般没啥用。

当我们需要调用一个对象的多个方法时,为了简化写法可以省略掉多个对象名称,这是可以考虑使用 with 函数,例如 Recycleriew 中绑定 ViewHolder 的操作,一般情况是这样的:

override fun convert(viewHolder: ViewHolder, data: DatasItem, position: Int) {
        ImageLoader.load(mContext, data.envelopePic, viewHolder.getView(R.id.projectIv))
        viewHolder.setText(R.id.projectTitleTv, Html.fromHtml(data.title).toString())
        viewHolder.setText(R.id.projectDescTv, data.desc)
        viewHolder.setText(R.id.projectAuthorTv, data.author)
        viewHolder.setText(R.id.projectTimeTv, data.niceDate)
    }

如果使用了 with 函数会是这样的:

override fun convert(viewHolder: ViewHolder, data: DatasItem, position: Int) {
        with(viewHolder){
            ImageLoader.load(mContext, data.envelopePic, getView(R.id.projectIv))
            setText(R.id.projectTitleTv, Html.fromHtml(data.title).toString())
            setText(R.id.projectDescTv, data.desc)
            setText(R.id.projectAuthorTv, data.author)
            setText(R.id.projectTimeTv, data.niceDate)
        }
    }

四、run

run 函数的声明如下:

@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

两种声明,第一个没啥大用,直接看第二个,是不是有点像 let、 with 函数的结合体呢!需要通过一个对象调用,可已接收一个 Lambda 表达式作为参数,那么返回值自然是 Lambda 表达式的返回值。

和 with 函数类似在 Lambda 表达式中可以用this指代要操作的对象或者省略 this 也行,而无需使用it,同时也具备了 let 函数可以进行对象判空的优点,例如 Android 中 Toolbar 的初始化操作:

toolbar.run {
            title = "设置"
            setSupportActionBar(this)
            setNavigationOnClickListener {
                finish()
            }
            supportActionBar?.setDisplayHomeAsUpEnabled(true)
        }

将相关的操作集中在一个代码块里,代码逻辑会更加的清晰。

五、apply

apply 函数的声明如下:

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

apply 函数和 run 函数很像,唯一的区别就是 apply 函数返回调用它的对象本身。一般情况下,如果需要创建一个对象,并在相关初始化操作后赋值给一个变量可以考虑使用 apply 函数。例如 Fragment 的 newInstance()方法传递参数时:

companion object {
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
                TestFragment().apply {
                    arguments = Bundle().apply {
                        putString(ARG_PARAM1, param1)
                        putString(ARG_PARAM2, param2)
                    }
                }
    }

其实,这五个函数中,根据是否需要对象的返回值来划分需求,只使用 run、apply 函数就可以替代其它函数的使用场景。当然合适的才是最好的,按需选择即可!它们的主要语法差别的如下:

函数 Lambda 表达式中如何指代当前对象 返回值
let it Lambda 表达式的值
also it 当前对象
with this(可省略) Lambda 表达式的值
run this(可省略) Lambda 表达式的值
aplly this(可省略) 当前对象

六、takeIf

takeIf 函数的声明如下:

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}

我们可以通过一个对象来调用它,如果predicate函数返回值为true,则返回调用对象,否则返回null,注意predicate函数的参数就是当前调用对象。

这其实就是一个加强版的if表达式,更加灵活,我们可以让对象使用安全调用操作符?.,由于 takeIf 函数可以返回对象本身,那么自然可以进行链式调用。写个例子简单比较下:

fun filterUser1(user: User?) {
    if (user != null && user.age > 18 && user.sex == "male") {
        println(user.toString())
    }
}

fun filterUser2(user: User?) {
    user?.takeIf {
        it.age > 18 && it.sex == "male"
    }.apply {
        println(toString())
    }
}

七、takeUnless

takeUnless 函数的声明如下:

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}

嗯?如果predicate函数返回值为false,则返回调用对象,否则返回null,功能和 takeIf 函数相反!

八、repeat

repeat 函数声明如下:

@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }

    for (index in 0 until times) {
        action(index)
    }
}

就是将action函数执行times次,函数的参数就是当前的次数:

fun main(args: Array<String>) {
    repeat(6) {
        println("Kotlin$it")
    }
}
// 输出
Kotlin0
Kotlin1
Kotlin2
Kotlin3
Kotlin4
Kotlin5

九、TODO

TODO 函数的声明如下:

@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()

@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")

和 Java 中的TODO类似,可以用来标注某个方法需要重写,或者没有完成的事项等等,但是 Kotlin 的 TODO 会抛出异常,并可以指定异常原因!

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

推荐阅读更多精彩内容

  • 相比Java, Kotlin提供了不少高级语法特性。对于一个Kotlin的初学者来说经常会写出一些不够优雅的代码。...
    vb12阅读 2,065评论 0 12
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,350评论 0 5
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,062评论 9 118
  • 文/伊若 时常想起那天傍晚,走在苏堤边的柳树下,那清晰却混着湖水味儿的气息,滑过鼻翼,满目竟是那断桥上的深情一幕。...
    伊若阅读 324评论 2 3
  • 今天,我闲来无事清理相册,无意识地翻到了一些照片和视频,瞬间便心潮涌动,鼻子发酸,眼角湿润,痛苦到无法呼吸…… 那...
    Aylin楚吟阅读 268评论 21 54