Kotlin Standard.kt 内置函数使用

作者: SheHuan | 来源:发表于2018-12-10 12:31 被阅读10次

    在 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 会抛出异常,并可以指定异常原因!

    相关文章

      网友评论

        本文标题:Kotlin Standard.kt 内置函数使用

        本文链接:https://www.haomeiwen.com/subject/zdosxqtx.html