美文网首页Android开发经验谈Android技术知识Kotlin编程
Kotlin 常用作用域函数,区别和使用

Kotlin 常用作用域函数,区别和使用

作者: chendroid | 来源:发表于2019-07-24 11:38 被阅读2次

    1. 有关作用域函数:

    Kotlin 中有很多作用域函数, 但是常见的使用场景是不同的。

    常见的五种作用域函数 let, run also, apply, with, 所有的对象均可使用这些函数。

    四种作用域函数,最直观的区别是:

    1. let, run 会直接返回闭包的返回结果;

      源码如下:

      // let 函数, 返回结果是 block, 且 T 作为闭包参数 「`(T)`」
      public inline fun <T, R> T.let(block: (T) -> R): R {
        contract {
           callsInPlace(block, InvocationKind.EXACTLY_ONCE)
          }
          return block(this)
       }
      
      // run 函数如下, 闭包没有参数
      public inline fun <T, R> T.run(block: T.() -> R): R {
          contract {
              callsInPlace(block, InvocationKind.EXACTLY_ONCE)
          }
          return block()
      }
      

      同时从代码中,可以看到 let 中,T 是作为 block 的一个参数「(T)」,
      run 则没有闭包参数 「T.()

    1. also, apply 闭包返回的结果是调用者本身

      源码实现:

      // alse 函数, 返回结果是 this, 且 T 作为闭包参数 「`(T)`」
      public inline fun <T> T.also(block: (T) -> Unit): T {
          contract {
              callsInPlace(block, InvocationKind.EXACTLY_ONCE)
          }
          block(this)
          return this
      }
      
      // apply 函数, 没有闭包参数
      public inline fun <T> T.apply(block: T.() -> Unit): T {
          contract {
              callsInPlace(block, InvocationKind.EXACTLY_ONCE)
          }
          block()
          return this
      }
      

      因为 also, apply 的 返回值为当前调用者本身, 所以比较方面进行链式调用,和组合使用。

      test.also{
          println("first also")
      }.also {
          println("second also")
      }.apply {
          name = "hahaha"
      }.also {
          println("third also")
      }
      

      注: 在上面的 applyrun 中,闭包的形式为 T.() 也就是说,在调用时,在 {} 中是可以直接访问 T 里面的方法和变量的 updateView(),而不需要显示的写为 T.updateView()

    1. let, run also, apply, with 在闭包中的引用符号差别 itthis

      从上面的源码中可以发现: let, also 它的调用者是闭包的参数,可以用 it 引用该调用者:

      var test : Test? = null
      test?.let {
          println(it.name)
      }
      

      runapply 闭包是无参的忙, 可以用 this 来引用该调用者:

      var test : Test? = null
      test?.run {
          // 该 name 为 Test 对象的属性
          println(name)
          // 有时为来区分 name 和这个 闭包外面的成员变量 name, 为了代码可读性, 也可使用下面的方式
          println(this@run.name)
      }
      

    场景使用

    1 alsoapply 的调用场景:

    两个都默认返回调用者本身。

    1.1 also 更注重的是:Additional effects 额外效果

    即:一个对象在调用中,做的其他的额外处理。

    test?.also {
       println("复值前$it.name")
    }?.apply {
       name = "hahaha"
    }?.also {
       println("复值后")
    }
    

    当要处理的逻辑不涉及调用者 T 本身时,使用 also.

    1.2 当使用场景是 Object configuration 对象参数配置, 使用 apply

    当是对一个对象进行参数配置时「可连续配置,链式调用」,可以使用该方式:

    return AnswerDividerItemDecoration2.instance(context).apply {
                 dividerHeight = 10.dp
                 setPaintColor(R.color.xxx)
                 setPaintLightColor(R.color.xxx)
    }
    

    以上代码是返回结果是一个 AnswerDividerItemDecoration2 对象 instance

    AnswerDividerItemDecoration2.instance(context) 返回的对象就是 instance, 我们利用 apply 对其进行了配置,并且闭包返回调用者本身 instance, 再利用 return 返回 。

    2. letrun 的调用场景:

    两者都返回闭包最后的执行结果。

    1. 当不需要返回调用者 T 本身时,可以使用两者;
    2. 当对闭包的返回结果不关心时,可以使用两者;「个人更喜欢用 run
    3. 当需要对闭包最后的执行结果操作时,可以使用两者;「个人更喜欢用 run

    2.1 let 的使用偏向:

    当使用 run 时调用者的一些属性和外部的属性重合时可以使用 letit

    例如:

    // it.name 指的是 test 对象里面的属性 name; 
    test?.let { 
         it.name = name // 这个 name 指的是当前方法外部的成员变量 name
         it.count = count 
    }
    

    2.2 run 的使用偏向:

    当涉及到很多复杂的逻辑时需要使用该方式 run

    例如代码:

    fun test4() {
        val beanList = mutableListOf<BeanTest>()
        beanList.run {
            println("beanList 转化数据为 bean1List")
            toBean1List(this)
        }.run {
            println("bean1List 转化数据为 bean2List")
            toBean2List(this)
        }
    }
    

    上述代码, 通过 run, 把 toBean1List() 的返回值 bean1List 作为调用者再次调用了 run.

    这也是 run 的优势,可以非常便捷的执行复杂的逻辑操作,数据转换等工作。

    2.3 当仅仅用作判空处理时:不推荐使用 let

    代码示例如下:

    fun test2(str:String?) {
        if (str != null) {
            // do something,与 str 变量无关
            println("hahaha")
        }
        str?.let {
            // do something,与 str 变量无关
            println("hahaha ---> let")
        }
    }
    

    上面两种方式,看起来差别不大,但是当 kotlin 被编译成 java 代码时:

     public final void test2(@Nullable String str) {
          if (str != null) {
             String var2 = "hahaha";
             System.out.println(var2);
          }
    
          if (str != null) {
             int var4 = false;
             String var5 = "hahaha ---> let";
             System.out.println(var5);
          }
    
       }
    

    会发现,使用 let 的里面多了一行 int var4 = false;
    引入了一个新的变量,这在该方法被多次调用时,是会占用内存的~
    所以,当仅仅是用作判断是否为 null 时,可简单的使用 if() 去判断。

    2.4 当需要对调用者 T 对象里面的参数进行赋值时,且不涉及到外部的内容,使用 run

    例如:

    // 不推荐这么使用
    webviewSetting?.let {
        it.javaScriptEnabled = true
        it.databaseEnabled = true
    }
    
    // 推荐使用 run
    webviewSetting?.run {
        javaScriptEnabled = true
        databaseEnabled = true
    }
    

    上面两种方式,本质上没有差别,但是第二种方式消除了 it 的使用, 代码上更舒服。

    2.5 当为了去除调用者多个 ?. 判断时,可以使用 run

    例如代码:

    
    fun test3(str: String?) {
        // 第一种方式, 需要每个都需要判断是否空
        str?.asIterable()?.iterator()?.next()
        // 第二种方式, 使用 `!!` 强制断言不为 null, 这样可以省去 ? 判断
        str!!.asIterable().iterator().next()
            
        // 第三种方式,推荐使用
        str?.run {
            asIterable().iterator().next()
        }
        // 第四种方式
        if (str != null) {
            str.asIterable()
        }
    }
    

    上面代码中,一共有三种方式,

    1. 第一种是不友好的方式,有很多冗余的判断;
    2. 第二种方式,也不太好,因为使用 !! 如果为空,直接 crash, 慎用 !!
    3. 第三种方法推荐使用,更简洁;
    4. 第四种方法 ide 检测会报红的,因为 if(str != null) 里面 在 kotlin 并不能自动把 str 转变为非 null

    参考链接:

    1. Kotlin standard functions -- Medium
    2. Kotlin 作用域函数 -- 简书
    3. let 的使用 -- Medium

    相关文章

      网友评论

        本文标题:Kotlin 常用作用域函数,区别和使用

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