专栏首页恩蓝脚本Kotlin中标准函数run、with、let、also与apply的使用和区别详解

Kotlin中标准函数run、with、let、also与apply的使用和区别详解

前言

和Java相比,在Kotlin中提供了不少新的特性。这次我们就来聊一聊Kotlin的一些通用的扩展标准函数run,with,let,also和apply。对于这五个标准函数它们都存在于Kotlin的源码标准库当中,也就是在Standard.kt文件当中。它们都是适用于任何对象的通用扩展函数。但是对于run,with,let,also和apply这五个函数他们的用法及其相似,以至于我们无法确定去选择使用哪一个。那么现在我们就来聊一下这五个函数它们的使用方法,它们的不同之处以及在什么场景下去使用。

作用域函数

在这里我们重点是看一下run,with,T.run,T.let,T.also,和T.apply,对于这几个函数来说它们最重要的功能之一是在调用函数的内部又提供了一个作用域。

那么下面就通过一段代码来看一下run函数的作用域,对于其它函数来说当然也是类似。

fun test(){
 var animal = "cat"
 run {
  val animal = "dog"
  println(animal) // dog
 }
 println(animal)  //cat
}

在这个简单的test函数当中我们拥有一个单独的作用域,在run函数中能够重新定义一个animal变量,并且它的作用域只存在于run函数当中。

目前对于这个run函数看起来貌似没有什么用处,但是在run函数当中它不仅仅只是一个作用域,他还有一个返回值。他会返回在这个作用域当中的最后一个对象。

例如现在有这么一个场景,用户领取app的奖励,如果用户没有登录弹出登录dialog,如果已经登录则弹出领取奖励的dialog。我们可以使用以下代码来处理这个逻辑。

run {
 if (islogin) loginDialog else getAwardDialog
}.show()

可以看到上面这段代码会变得更加的简洁,并且可以将show方法一次应用到上面两个dialog当中,而不是去调用两次。

with和其它通用标准函数

在这里之所以将with函数单独拿出来进行说明,是因为with得用法和其它通用的标准函数的用法比较独特。在这里我们依然使用run函数来进行对比。对于下面这段代码做的是同样一件事。它们的不同之处就是一个使用了with(T)函数,而另一个则是使用了T.run函数。

with(webView.settings){
 javaScriptEnabled = true
 databaseEnabled = true
}
webView.settings.run { 
 javaScriptEnabled = true
 databaseEnabled = true
}

但是我们觉得使用哪一个会更好呢?现在假设一种场景,那就是webView.settings可能为null。那我们就来再次看一下下面这段代码.

with(webView.settings){
 javaScriptEnabled = true
 databaseEnabled = true
}
webView.settings?.run { 
 javaScriptEnabled = true
 databaseEnabled = true
}

这么以来就很明显了,当然是T.run方法会更好,因为我们可以在使用这些函数之前可以进行对null的检查。

对于with也是存在一个返回值,它也是会返回在这个作用域当中的最后一个对象。

作用域中接收者this和it

在这几个扩展函数当中,它们都能直接获取到调用的对象或者是with中传入参数的对象。在这五个扩展函数在它们的作用域中的接收者可以是this或者是it。那么我们来对比一下T.run和T.let函数。这两个函数也是十分的相似。

stringVariable?.run {
 println("字符串的长度为$length")
}

stringVariable?.let {
 println("字符串的长度为 ${it.length}")
}

在这两段代码中可以清晰的看到。在T.run函数中通过this来获取stringVariable对象,而在T.let函数中通过it来取出stringVariable对象。当然我们也能够为it重新命名。如果我们不想覆盖外部作用域的this,这时候去使用T.let会更加的方便。至于哪些函数的接收者是this,哪些函数的接收者是it,在后面会通过一张树状图清晰的体现出来。

在作用域中返回值的类型

在这些作用域中它们都会存在一个返回值。在上面的讲述的run,with,T.run,T.let中它们返回的都是作用域中最后一个对象。当然它们所返回的值是允许和接受者it或者this对象的类型不同。但是并不是所有的标准函数都是返回作用域的最后一个对象。例如T.also函数。

val original = "abc"
original.let {
 println("The original String is $it") // "abc"
 it.reversed() 
}.let {
 println("The reverse String is $it") // "cba"
 it.length 
}.let {
 println("The length of the String is $it") // 3
}

original.also {
 println("The original String is $it") // "abc"
 it.reversed() 
}.also {
 println("The reverse String is ${it}") // "abc"
 it.length 
}.also {
 println("The length of the String is ${it}") // "abc"
}

从上面两段代码可以看出T.let和T.also的返回值使不同的。T.let返回的是作用域中的最后一个对象,它的值和类型都可以改变。但是T.also不管调用多少次返回的都是原来的original对象。

对于T.let和T.also都能够进行链式操作,那么我们现在结合一下T.let和T.also的链式调用来看一下在实际场景中的应用。

//原始函数
fun makeDir(path: String): File {
 val result = File(path)
 result.mkdirs()
 return result
}

//通过let和also的链式调用改进后的函数
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

扩展函数的特性

到目前为止除了T.apply没有使用到以外,根据上面的用法我们可以总结出来这些标准函数的三大特性。

  • 它们都有自己的作用域
  • 它们作用域中的接收者是this或者it
  • 它们都有一个返回值,返回最后一个对象(this)或者调用者自身(itself)

由此可想到对于T.apply无非也就是这三个特性。对于T.apply它作用域中的接收者是this,并且返回的调用者T。因此,T.apply的其中一个使用场景可以用来创建一个Fragment,代码如下所示:

// 使用普通的方法创建一个Fragment
fun createInstance(args: Bundle) : MyFragment {
 val fragment = MyFragment()
 fragment.arguments = args
 return fragment
}

// 通过apply来改善原有的方法创建一个Fragment
fun createInstance(args: Bundle) 
    = MyFragment().apply { arguments = args }

我们也能够通过T.apply的链式调用创建一个Intent:

// 普通创建Intent方法
fun createIntent(intentData: String, intentAction: String): Intent {
 val intent = Intent()
 intent.action = intentAction
 intent.data=Uri.parse(intentData)
 return intent
}

// 通过apply函数的链式调用创建Intent
fun createIntent(intentData: String, intentAction: String) =
  Intent().apply { action = intentAction }
    .apply { data = Uri.parse(intentData) }

如何选择使用

在这里我们通过一个树状图来看一下对着五个标准函数的区别,使用以及如何选取标准函数(图片来源于参考文献当中)

总结

在这里做一下总结,我们可以看出在这五个通用标准函数当中它们的特性也是十分的简单,无非也就是接收者和返回值的不同。对于with,T.run,T.apply接收者是this,而T.let和T.also接受者是it;对于with,T.run,T.let返回值是作用域的最后一个对象(this),而T.apply和T.also返回值是调用者本身(itself)。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对ZaLou.Cn的支持。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android 项目正式签名打包教程分享

    大家在开发安卓应用的时候,在调试阶段通常都是通过 run 的方式发布到模拟器或者真机上,我们知道 android 应用打包后的后缀名是 .apk 文件。.apk...

    砸漏
  • Python命名空间namespace及作用域原理解析

    就像有A(4个苹果),B(6个苹果)两个人,10个苹果,如果只标签了苹果,你无法判断哪个苹果是属于哪个人的,因为标签都是一样的;但是如果标签是A.苹果,B.苹果...

    砸漏
  • Diycode开源项目实例搭建上拉加载和下拉刷新的Fragment

    以下通过3个知识点给大家讲解了上拉加载和下拉刷新的Fragment实现的方法,在对每个知识点介绍了一下用法。

    砸漏
  • 高性能的JavaScript--数据访问(1)

    数据存储在哪里,关系到代码运行期间数据被检索到的速度。在JavaScript中,此问题相对简单,因为数据存储只有少量方式可供选择。正如其他语言那样,数据存储位置...

    DougWang
  • C++作用域与生命周期

    Pascal之父Nicklaus Wirth曾经提出一个公式,展示出了程序的本质:程序=算法+数据结构。后人又给出一个公式与之遥相呼应:软件=程序+文档。这两个...

    Dabelv
  • C++中的作用域与生命周期

    Pascal之父Nicklaus Wirth曾经提出一个公式,展示出了程序的本质:程序=算法+数据结构。后人又给出一个公式与之遥相呼应:软件=程序+文档。这两个...

    Dabelv
  • 作用域和作用域链的简单理解

    javascript采用的静态作用域,也可以称为词法作用域,意思是说作用域是在定义的时候就创建了, 而不是运行的时候。此话对于初学者很不好理解,看看下面这个例子...

    ZEHAN
  • JavaScript 作用域不完全指北

    对于几乎所有编程语言,最基本的功能之一就是能够存储变量的值,并且能在之后对这个值进行访问和修改。这样就会带来几个问题,这些变量存储在哪里?程序在需要的时候又是如...

    撸码那些事
  • 稳扎稳打JavaScript(一)——作用域链内存模型

    几个概念 在开始之前,先了解几个概念。 1.1. 作用域 作用域是指当前正在执行的代码能够访问到变量的范围; 每个函数都有各自的作用域,存储函数所有的局部变量...

    大闲人柴毛毛
  • 高性能Javascript--高效的数据访问

    Sb_Coco

扫码关注云+社区

领取腾讯云代金券