美文网首页
Kotlin:作用域函数

Kotlin:作用域函数

作者: SirWwh | 来源:发表于2019-03-22 17:46 被阅读0次
    Kotlin

    前言

    最近使用kotlin语言开发了新的项目,kotlin的一些特性和大量的语法糖相当好用,相比于java,开发效率高了不少。但Kotlin大量的语法糖也带来了一些问题:学习成本高,语法糖使用场景的困惑。
    比如,当我第一次看到作用域函数就产生了这样的疑问:what is this?Which function to use?

    于是我研究了一下什么是作用域函数,以及各个函数的区别和使用场景。

    介绍

    官方介绍:The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an object with a lambda expression provided, it forms a temporary scope. In this scope, you can access the object without its name. Such functions are called scope functions. There are five of them: let, run, with, apply, and also.

    翻译理解:作用域函数的目的是在对象的上下文中执行代码块,它为调用者对象提供了一个临时内部作用域,在这个作用域中可以不显式的访问该对象。这样的作用域函数有5个:let,run,with,apply,和also。

    函数

    run

    run函数是最能体现作用域的用途的函数,如下使用示例:
    在mian函数中使用run函数创建了一个单独的作用域,在该作用域中重新定义了一个word变量,两次打印使用的是各自作用域中的word变量,互不影响;并且,run函数返回了lambda结果。

    使用示例

    fun main(args: Array<String>) {
        var word = "我是小明"
        val returnValue = run {
            var word = "我是小红"
            println("run:$word")
            word
        }
        println("main:$word")
        println("returnValue:$returnValue")
    }
    

    运行结果:

    run:我是小红
    main:我是小明
    returnValue:我是小红
    

    with

    with函数可以将任意对象作为上下文对象this传入,并且可以隐式的访问该对象,返回lambda结果。如下使用示例:在mian函数中使用with函数创建了一个临时作用域,在该作用域中可以重新定义person变量,两个person变量互无影响;并且可以使用this访问上下文对象,隐式修改person的age变量值。

    使用示例

    data class Person (
        var name: String,
        var age: Int = 0
    )
    fun main(args: Array<String>) {
        var person = Person("小明",25)
        val returnValue = with(person) {
            println("with:this=$this")
            var person = Person("小红",23)
            println("with:person=$person")
            age = 26
            person
        }
        println("main:person=$person")
        println("main:returnValue=$returnValue")
    }
    

    运行结果:

    with:this=Person(name=小明, age=25)
    with:person=Person(name=小红, age=23)
    main:person=Person(name=小明, age=26)
    main:returnValue=Person(name=小红, age=23)
    

    T.run

    T.run函数可以使用T作为作用域的上下文对象this,在作用域中可以隐式访问T对象,并返回lambda结果。

    使用示例

    data class Person (
        var name: String,
        var age: Int = 0
    )
    fun main(args: Array<String>) {
        var person: Person? = null
        // T?.run当T为null时不调用run函数
        person?.run {
            println("person?.run:person=$person")
        }
        person = Person("小明",25)
        val returnValue = person.run {
            println("person.run:this=$this")
            var person = Person("小红",23)
            println("person.run:person=$person")
            age = 26
            person
        }
        println("main:person=$person")
        println("main:returnValue=$returnValue")
    }
    

    运行结果:

    person.run:this=Person(name=小明, age=25)
    person.run:person=Person(name=小红, age=23)
    main:person=Person(name=小明, age=26)
    main:returnValue=Person(name=小红, age=23)
    

    T.let

    T.let函数与T.run函数唯一的区别是:T作为作用域上下文对象的名称不同,前者是it,后者是this,所以在T.let函数中必须显式使用it访问T对象。

    使用示例

    data class Person (
        var name: String,
        var age: Int = 0
    )
    fun main(args: Array<String>) {
        var person: Person? = null
        person?.let {
            println("person?.let:person=$person")
        }
        person = Person("小明",25)
        val returnValue = person.let {
            println("person.let:it=$it")
            var person = Person("小红",23)
            println("person.let:person=$person")
            it.age = 26
            person
        }
        println("main:person=$person")
        println("main:returnValue=$returnValue")
    }
    

    运行结果:

    person.let:it=Person(name=小明, age=25)
    person.let:person=Person(name=小红, age=23)
    main:person=Person(name=小明, age=26)
    main:returnValue=Person(name=小红, age=23)
    

    T.also

    如下使用示例,T.also函数和T.let函数的唯一区别是:前者返回值是this(即T),后者返回值是lambda结果。

    使用示例

    data class Person (
        var name: String,
        var age: Int = 0
    )
    fun main(args: Array<String>) {
        var person: Person? = null
        person?.also {
            println("person?.also:person=$person")
        }
        person = Person("小明",25)
        val returnValue = person.also {
            println("person.also:it=$it")
            var person = Person("小红",23)
            println("person.also:person=$person")
            it.age = 26
            person
        }
        println("main:person=$person")
        println("main:returnValue=$returnValue")
    }
    

    运行结果:

    person.also:it=Person(name=小明, age=25)
    person.also:person=Person(name=小红, age=23)
    main:person=Person(name=小明, age=26)
    main:returnValue=Person(name=小明, age=26)
    

    T.apply

    如下使用示例,T.apply函数和T.also函数的唯一的区别是:T作为作用域上下文对象的名称不同,前者是this,后者是it,所以在T.apply函数中可以隐式访问T对象。

    使用示例

    data class Person (
        var name: String,
        var age: Int = 0
    )
    fun main(args: Array<String>) {
        var person: Person? = null
        person?.apply {
            println("person?.apply:person=$person")
        }
        person = Person("小明",25)
        val returnValue = person.apply {
            println("person.apply:this=$this")
            var person = Person("小红",23)
            println("person.apply:person=$person")
            age = 26
            person
        }
        println("main:person=$person")
        println("main:returnValue=$returnValue")
    }
    

    运行结果:

    person.apply:this=Person(name=小明, age=25)
    person.apply:person=Person(name=小红, age=23)
    main:person=Person(name=小明, age=26)
    main:returnValue=Person(name=小明, age=26)
    

    特殊的作用域函数

    T.takeIf

    以it作为在作用域上下文对象T的名称,若lambda结果为true,返回this;否则,返回null。

    函数源码

    @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
    }
    

    使用示例

    fun main(args: Array<String>) {
        var count = 0
        while (count <= 10) {
            val returnValue = count.takeIf {
                count++ % 2 == 0
            }
            println(returnValue)
        }
    }
    

    运行结果:

    0
    null
    2
    null
    4
    null
    6
    null
    8
    null
    10
    

    T.takeUnless

    以it作为在作用域上下文对象T的名称,若lambda结果为true,返回null;否则,返回this。与taskIf的实现相比,其实就是对lambda结果进行了取反操作。

    函数源码

    @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
    }
    

    使用示例

    fun main(args: Array<String>) {
        var count = 0
        while (count <= 10) {
            val returnValue = count.takeUnless {
                count++ % 2 == 0
            }
            println(returnValue)
        }
    }
    

    运行结果:

    null
    1
    null
    3
    null
    5
    null
    7
    null
    9
    null
    

    repeat

    以当前执行的次数it作为在作用域上下文对象T的名称,执行给定lambda函数指定的次数。从函数源码和使用示例可以看出,执行次数角标是从0开始。

    函数源码

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

    使用示例

    fun main(args: Array<String>) {
        repeat(5) {
            print("$it,")
        }
    }
    

    运行结果:

    0,1,2,3,4,
    

    总结

    从上面的函数介绍和实际使用可以看出let,run,with,apply,和also,这些作用域函数的功能之间起着相互补充的作用,单独看某两个函数可能差别不大,但它们结合起来所实现的功能涵盖了绝大部分的使用场景。

    总结一下,用于快速判断操作符使用场景,主要使用这几个因素辨别:

    1. 调用者

      • 正常函数:有run,with函数。主要作用是:开辟一个作用域,不受作用域之外上下文影响,with还可以方便地在作用域中访问上下文对象。
      • 扩展函数:可以使用T?.fun()在调用之前做空检查,如:null?.run { println("Kotlin") },作用域内容不会被执行。
    2. 上下文对象

      • this:方便在作用域中直接访问this
      • it:可以更清楚的区分作用域和非作用域中的成员
    3. 返回值

      • 上下文对象this:可以作为链式调用。
      • lambda表达式结果:返回表达式结果,可以将结果结合其他作用域函数,使用更灵活。
      // 示例:使用apply函数进行链式调用
      class Person {
          var name = ""
          var age = 0
      }
      fun main(args: Array<String>) {
          val person = Person().apply { name = "小明" }.apply { age = 25 }
          println("${person.name},${person.age}")
      }
      // 运行结果:小明,25
      

    下面对作用域函数简要区分,可以更方便快速的辨别各函数的作用和使用场景。

    作用域函数简要区分:

    • run:返回lambda结果
    • with:this上下文,返回lambda结果
    • T.run:支持空检查,this上下文,返回lambda结果
    • T.let:支持空检查,it上下文,返回lambda结果
    • T.also:支持空检查,it上下文,返回this(即T,it)
    • T.apply:支持空检查,this上下文,返回this(即T,this)

    特殊的作用域函数区分:

    • T.takeIf:支持空检查,it上下文,函数体返回值类型Boolean,函数体返回true,函数返回this;否则返回null
    • T.takeUnless:支持空检查,it上下文,函数体返回值类型Boolean,函数体返回true,函数返回null;否则返回this
    • repeat:执行给定函数 action 指定的次数 times (角标:0-times)

    参考资料

    官方文档:https://www.kotlincn.net/docs/reference/scope-functions.html
    medium Elye:https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84
    CSDN george_zyf:https://blog.csdn.net/android_zyf/article/details/82496983

    相关文章

      网友评论

          本文标题:Kotlin:作用域函数

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