作用域函数

Kotlin 的作用域函数有五种:let、run、with、apply 以及 also。

这些函数基本上做了同样的事情:在一个对象上执行一个代码块。

下面是作用域函数的典型用法:

val adam = Person("Adam").apply { 
 age = 20
 city = "London"
}
println(adam)

如果不使用 apply 来实现,每次给新创建的对象属性赋值时就必须重复其名称。

val adam = Person("Adam")
adam.age = 20
adam.city = "London"
println(adam)

作用域函数没有引入任何新的技术,但是它们可以使你的代码更加简洁易读。

事实上,同样的功能可能多个作用域函数都能实现,但我们应该根据不同的场景和需求来使用合适的作用域函数,以求更优雅的实现。

如果想直接查看作用域函数之间的区别与使用场景归纳表,请点击这里。

下面我们将详细描述这些作用域函数的区别及约定用法。

区别

作用域函数之间有两个主要区别:

  • 引用上下文对象的方式
  • 返回值

引用上下文对象的方式:this 还是 it

在作用域函数的 lambda 表达式里,上下文对象可以不使用其实际名称而是使用一个更简短的引用(this 或 it)来访问。
作用域函数引用上下文对象有两种方式:

  • 作为 lambda 表达式的接收者(this): run、with、apply
  • 作为 lambda 表达式的参数(it): let、also
fun main() {
 val str = "Hello"
 // this
 str.run {
 println("The receiver string length: $length")
 //println("The receiver string length: ${this.length}") // 和上句效果相同
 }

 // it
 str.let {
 println("The receiver string's length is ${it.length}")
 }
}

作为 lambda 表达式的接收者

run、with 以及 apply 将上下文对象作为 lambda 表达式接收者,通过关键字 this 引用上下文对象。可以省略 this,使代码更简短。

使用场景:主要对上下文对象的成员进行操作(访问属性或调用函数)。

val adam = Person("Adam").apply { 
 age = 20  // 和 this.age = 20 或者 adam.age = 20 一样
 city = "London"
}
println(adam)

作为 lambda 表达式的参数

let 及 also 将上下文对象作为 lambda 表达式参数。如果没有指定参数名,对象可以用隐式默认名称 it 访问。it 比 this 简短,带有 it 的表达式通常更容易阅读。然而,当调用对象函数或属性时,不能像 this 这样隐式地访问对象。

使用场景:主要对上下文对象进行操作,作为参数使用。

fun getRandomInt(): Int {
 return Random.nextInt(100).also {
 writeToLog("getRandomInt() generated value $it")
 }
}
val i = getRandomInt()

此外,当将上下文对象作为参数传递时,可以为上下文对象指定在作用域内的自定义名称(为了提高代码的可读性)。

fun getRandomInt(): Int {
 return Random.nextInt(100).also { value ->
 writeToLog("getRandomInt() generated value $value")
 }
}
val i = getRandomInt()

返回值

根据返回结果,作用域函数可以分为以下两类:

  • 返回上下文对象:apply、also
  • 返回 lambda 表达式结果:let、run、with

可以根据在代码中的后续操作来选择适当的函数。

返回上下文对象

apply 及 also 的返回值是上下文对象本身。因此,它们可以作为辅助步骤包含在调用链中:你可以继续在同一个对象上进行链式函数调用。

val numberList = mutableListOf<Double>()
numberList.also { println("Populating the list") }
 .apply {
 add(2.71)
 add(3.14)
 add(1.0)
 }
 .also { println("Sorting the list") }
 .sort()

它们还可以用在返回上下文对象的函数的 return 语句中。

fun getRandomInt(): Int {
 return Random.nextInt(100).also {
 writeToLog("getRandomInt() generated value $it")
 }
}

val i = getRandomInt()

返回lambda表达式结果

let、run 及 with 返回 lambda 表达式的结果。所以,在需要使用其结果给一个变量赋值,或者在需要对其结果进行链式操作等情况下,可以使用它们。

val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run { 
 add("four")
 add("five")
 count { it.endsWith("e") }
}
println("There are $countEndsWithE elements that end with e.")

此外,还可以忽略返回值,仅使用作用域函数为变量创建一个临时作用域。

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
 val firstItem = first()
 val lastItem = last() 
 println("First item: $firstItem, last item: $lastItem")
}

约定用法

let

上下文对象 作为 lambda 表达式的 参数(it)来访问。 返回值 是 lambda 表达式的结果。

let 可用于在调用链的结果上调用一个或多个函数。例如,以下代码打印对集合的两个操作的结果:

val numbers = mutableListOf("one", "two", "three", "four", "five")
val resultList = numbers.map { it.length }.filter { it > 3 }
println(resultList)

使用 let,可以写成这样:

val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let { 
 println(it)
 // 如果需要可以调用更多函数
} 

若代码块仅包含以 it 作为参数的单个函数,则可以使用方法引用(::)代替 lambda 表达式:

val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let(::println)

let 经常用于 仅使用非空值执行代码块。如需对非空对象执行操作,可对其使用安全调用操作符 ?. 并调用 let 在 lambda 表达式中执行操作。

val str: String? = "Hello" 
//processNonNullString(str) // 编译错误:str 可能为空
val length = str?.let { 
 println("let() called on $it")
 processNonNullString(it) // 编译通过:'it' 在 '?.let { }' 中必不为空
 it.length
}

使用 let 的另一种情况是引入作用域受限的局部变量以提高代码的可读性。如需为上下文对象定义一个新变量,可提供其名称作为 lambda 表达式参数来替默认的 it。

val numbers = listOf("one", "two", "three", "four")
val modifiedFirstItem = numbers.first().let { firstItem ->
 println("The first item of the list is '$firstItem'")
 if (firstItem.length >= 5) firstItem else "!"   firstItem   "!"
}.toUpperCase()
println("First item after modifications: '$modifiedFirstItem'")

with

一个非扩展函数:上下文对象作为参数传递,但是在 lambda 表达式内部,它可以作为接收者(this)使用。 返回值是lambda 表达式结果。

建议使用 with 来调用上下文对象上的函数,而不使用 lambda 表达式结果。 在代码中,with 可以理解为“对于这个对象,执行以下操作。”

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
 println("'with' is called with argument $this")
 println("It contains $size elements")
}

with 的另一个使用场景是引入一个辅助对象,其属性或函数将用于计算一个值。

val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
 "The first element is ${first()},"  
 " the last element is ${last()}"
}
println(firstAndLast)

run

上下文对象 作为 接收者(this)来访问。 返回值 是 lambda 表达式结果。

run 和 with 做同样的事情,但是调用方式和 let 一样——作为上下文对象的扩展函数.

当 lambda 表达式同时包含对象初始化和返回值的计算时,run 很有用。

val service = MultiportService("https://example.kotlinlang.org", 80)
val result = service.run {
 port = 8080
 query(prepareRequest()   " to port $port")
}
// 同样的代码如果用 let() 函数来写:
val letResult = service.let {
 it.port = 8080
 it.query(it.prepareRequest()   " to port ${it.port}")
}

除了在接收者对象上调用 run 之外,还可以将其用作非扩展函数。 非扩展 run 可以使你在需要表达式的地方执行一个由多个语句组成的块。

val hexNumberRegex = run {
 val digits = "0-9"
 val hexDigits = "A-Fa-f"
 val sign = " -"
 Regex("[$sign]?[$digits$hexDigits] ")
}
for (match in hexNumberRegex.findAll(" 1234 -FFFF not-a-number")) {
 println(match.value)
}

apply

上下文对象 作为 接收者(this)来访问。返回值 是上下文对象本身。

对于不返回值且主要在接收者(this)对象的成员上运行的代码块使用 apply。apply 的常见情况是对象配置。这样的调用可以理解为“将以下赋值操作应用于对象”。

val adam = Person("Adam").apply {
 age = 32
 city = "London" 
}
println(adam)

将接收者作为返回值,可以轻松地将 apply 包含到调用链中以进行更复杂的处理。

also

上下文对象作为 lambda 表达式的参数(it)来访问。 返回值 是上下文对象本身。

also 对于执行一些将上下文对象作为参数的操作很有用。 对于需要引用对象而不是其属性与函数的操作,或者不想屏蔽来自外部作用域的 this 引用时,请使用 also。

当在代码中看到 also 时,可以将其理解为“并且用该对象执行以下操作”。

val numbers = mutableListOf("one", "two", "three")
numbers
 .also { println("The list elements before adding new one: $it") }
 .add("four")

总结

下表总结了Kotlin作用域函数的主要区别与使用场景:

函数 对象引用 返回值 是否是扩展函数 使用场景
let it Lambda 表达式结果 1. 对一个非空对象执行 lambda 表达式
2. 将表达式作为变量引入为局部作用域中
run this Lambda 表达式结果 对象配置并且计算结果
run - Lambda 表达式结果 不是:调用无需上下文对象 在需要表达式的地方运行语句
with this Lambda 表达式结果 不是:把上下文对象当做参数 一个对象的一组函数调用
apply this 上下文对象 对象配置
also it 上下文对象 附加效果

参考资料

Kotlin语言中文网

好了,到此这篇关于Kotlin作用域函数之间的区别和使用场景的文章就介绍到这了,更多相关Kotlin作用域函数区别与使用场景内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Kotlin作用域函数之间的区别和使用场景详解的更多相关文章

  1. ios – 不同作用域中相同命名常量的链接器错误

    我有一个名为“ID_KEY”的常量,它在3个单独的.m文件的顶部声明,其中没有包含其他文件.声明如下:而其他两个类也是如此.但是我收到一个链接器错误抱怨同名的多个定义.我的问题是为什么链接器抱怨这个呢?

  2. 15.6 Swift局部引用

    /**局部引用和全局引用1.作用域2.生命周期*/varref:Int=Int.init/**定义一个变量或者常量,如果不是可选类型的话,一定要有初始值。所谓的局部引用就是在代码块里面的引用就是局部引用。作用域生命周期都在该代码块中;离它最近的括号*/iftrue{varref:Student=Student.initref.name="ZHangsan"}//超出作用域啦//ref.name="ZHangsan"

  3. Kotlin难点解析:extension和this指针

    扩展是Kotlin语言中使用非常简单的一个特性。关于这个问题,其实我之前的一篇文章[[Kotlin]LambdaandExtension](https://www.jianshu.com/p/d7a...中有提到过。为了解决这个问题,官方提出了两个新的概念:dispatchreceiver和extensionreceiver。extensionreceiver:中文翻译为扩展接收者。为了简化,这里我们将dispatchreceiver简称为DR,将extensionreceiver简称为ER。如果你习惯了

  4. android – Kotlin类NoClassDefFoundError崩溃

    我有一个使用以下库的现有Android项目:>Autovalue>Dagger2>RxJava>Retrolambda我正在尝试添加Kotlin支持,以便我可以将项目慢慢迁移到Kotlin.这就是我所做的.>添加了Kotlin依赖.>将其中一个类转换为Kt类并转移到src/main/kotlin/..包中.>在源集中添加了kotlin.sourceSets{main.java.srcDirs=’s

  5. android – Kotlin和Dagger2

    我正在尝试将Kotlin添加到我的项目中,但在启用Kotlin之后我无法构建,因为Dagger2类不再生成.我尝试了第二个项目,我有同样的问题.这些是我为启用Kotlin所做的改变:项目build.gradle:Appbuild.gradle:错误发生在这里:其中不再定义DaggerObjectGraph.任何帮助将不胜感激.解决方法只需删除

  6. android – 在Kotlin中不能使用argb color int值吗?

    当我想在Kotlin中为TextView的textColor设置动画时:发生此错误:似乎在Kotlin中不能将值0xFF8363FF和0xFFC953BE强制转换为Int,但是,它在Java中是正常的:有任何想法吗?提前致谢.解决方法0xFF8363FF是Long,而不是Int.你必须明确地将它们转换为Int:关键是0xFFC953BE的数值是4291384254,因此它应该存储在Long变量中.但这里的高位是符号位,表示负数:-3583042,可以存储在Int中.这就是两种语言之间的区别.在Kotlin

  7. 什么是我可以使用Kotlin的最早的Android API级别?

    我认为这个问题很清楚但是我能在Kotlin上定位的最早API级别是什么?解决方法实际上,任何API级别.这是因为Kotlin被编译为JVM6平台的字节码,所有AndroidAPI级别都支持该字节码.因此,除非您在Kotlin代码中使用任何较新的AndroidAPI,否则它不需要任何特定的API级别.

  8. android – Kotlin数据类和可空类型

    我是Kotlin的新手,我不知道为什么编译器会抱怨这段代码:编译器抱怨测试?.data.length,它说我应该这样做:test?.length.但是数据变量是String,而不是String?,所以我不明白为什么我要把它?当我想检查长度.解决方法表达式test?.data部分可以为空:它是test.data或null.因此,获取其长度并不是零安全的,而是应该再次使用safecalloperator:test?.length.可空性通过整个调用链传播:你必须将这些链写成?.)).e),因为,如果其中一个左

  9. android – Kotlin自定义获取执行方法调用

    像这样的东西:仍在使用Kotlin并且不确定get()方法是否会引用编辑器而不是创建新的编辑器.解决方法第二个属性声明适合您的需要:它有一个customgetter,因此获取属性值将始终执行getter,并且不存储该值.你可能会被等号get()=…

  10. android – Kotlin合成扩展和几个包含相同的布局

    我找了一些这样的:我在Studio中看到我可以访问dayName但是dayNameTextView引用了哪一个?正常,如果我只有一个包含的布局,它工作正常.但现在我有多次包含相同的布局.我当然可以这样做:但我正在寻找好的解决方案.版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部