Kotlin作用域函数之间的区别和使用场景详解
作用域函数
Kotlin的作用域函数有五种:let、run、with、apply以及also。
这些函数基本上做了同样的事情:在一个对象上执行一个代码块。
下面是作用域函数的典型用法:
valadam=Person("Adam").apply{ age=20 city="London" } println(adam)
如果不使用apply来实现,每次给新创建的对象属性赋值时就必须重复其名称。
valadam=Person("Adam") adam.age=20 adam.city="London" println(adam)
作用域函数没有引入任何新的技术,但是它们可以使你的代码更加简洁易读。
事实上,同样的功能可能多个作用域函数都能实现,但我们应该根据不同的场景和需求来使用合适的作用域函数,以求更优雅的实现。
如果想直接查看作用域函数之间的区别与使用场景归纳表,请点击这里。
下面我们将详细描述这些作用域函数的区别及约定用法。
区别
作用域函数之间有两个主要区别:
- 引用上下文对象的方式
- 返回值
引用上下文对象的方式:this还是it
在作用域函数的lambda表达式里,上下文对象可以不使用其实际名称而是使用一个更简短的引用(this或it)来访问。
作用域函数引用上下文对象有两种方式:
- 作为lambda表达式的接收者(this):run、with、apply
- 作为lambda表达式的参数(it):let、also
funmain(){ valstr="Hello" //this str.run{ println("Thereceiverstringlength:$length") //println("Thereceiverstringlength:${this.length}")//和上句效果相同 } //it str.let{ println("Thereceiverstring'slengthis${it.length}") } }
作为lambda表达式的接收者
run、with以及apply将上下文对象作为lambda表达式接收者,通过关键字this引用上下文对象。可以省略this,使代码更简短。
使用场景:主要对上下文对象的成员进行操作(访问属性或调用函数)。
valadam=Person("Adam").apply{ age=20//和this.age=20或者adam.age=20一样 city="London" } println(adam)
作为lambda表达式的参数
let及also将上下文对象作为lambda表达式参数。如果没有指定参数名,对象可以用隐式默认名称it访问。it比this简短,带有it的表达式通常更容易阅读。然而,当调用对象函数或属性时,不能像this这样隐式地访问对象。
使用场景:主要对上下文对象进行操作,作为参数使用。
fungetRandomInt():Int{ returnRandom.nextInt(100).also{ writeToLog("getRandomInt()generatedvalue$it") } } vali=getRandomInt()
此外,当将上下文对象作为参数传递时,可以为上下文对象指定在作用域内的自定义名称(为了提高代码的可读性)。
fungetRandomInt():Int{ returnRandom.nextInt(100).also{value-> writeToLog("getRandomInt()generatedvalue$value") } } vali=getRandomInt()
返回值
根据返回结果,作用域函数可以分为以下两类:
- 返回上下文对象:apply、also
- 返回lambda表达式结果:let、run、with
可以根据在代码中的后续操作来选择适当的函数。
返回上下文对象
apply及also的返回值是上下文对象本身。因此,它们可以作为辅助步骤包含在调用链中:你可以继续在同一个对象上进行链式函数调用。
valnumberList=mutableListOf() numberList.also{println("Populatingthelist")} .apply{ add(2.71) add(3.14) add(1.0) } .also{println("Sortingthelist")} .sort()
它们还可以用在返回上下文对象的函数的return语句中。
fungetRandomInt():Int{ returnRandom.nextInt(100).also{ writeToLog("getRandomInt()generatedvalue$it") } } vali=getRandomInt()
返回lambda表达式结果
let、run及with返回lambda表达式的结果。所以,在需要使用其结果给一个变量赋值,或者在需要对其结果进行链式操作等情况下,可以使用它们。
valnumbers=mutableListOf("one","two","three") valcountEndsWithE=numbers.run{ add("four") add("five") count{it.endsWith("e")} } println("Thereare$countEndsWithEelementsthatendwithe.")
此外,还可以忽略返回值,仅使用作用域函数为变量创建一个临时作用域。
valnumbers=mutableListOf("one","two","three") with(numbers){ valfirstItem=first() vallastItem=last() println("Firstitem:$firstItem,lastitem:$lastItem") }
约定用法
let
上下文对象作为lambda表达式的参数(it)来访问。返回值是lambda表达式的结果。
let可用于在调用链的结果上调用一个或多个函数。例如,以下代码打印对集合的两个操作的结果:
valnumbers=mutableListOf("one","two","three","four","five") valresultList=numbers.map{it.length}.filter{it>3} println(resultList)
使用let,可以写成这样:
valnumbers=mutableListOf("one","two","three","four","five") numbers.map{it.length}.filter{it>3}.let{ println(it) //如果需要可以调用更多函数 }
若代码块仅包含以it作为参数的单个函数,则可以使用方法引用(::)代替lambda表达式:
valnumbers=mutableListOf("one","two","three","four","five") numbers.map{it.length}.filter{it>3}.let(::println)
let经常用于仅使用非空值执行代码块。如需对非空对象执行操作,可对其使用安全调用操作符?.并调用let在lambda表达式中执行操作。
valstr:String?="Hello" //processNonNullString(str)//编译错误:str可能为空 vallength=str?.let{ println("let()calledon$it") processNonNullString(it)//编译通过:'it'在'?.let{}'中必不为空 it.length }
使用let的另一种情况是引入作用域受限的局部变量以提高代码的可读性。如需为上下文对象定义一个新变量,可提供其名称作为lambda表达式参数来替默认的it。
valnumbers=listOf("one","two","three","four") valmodifiedFirstItem=numbers.first().let{firstItem-> println("Thefirstitemofthelistis'$firstItem'") if(firstItem.length>=5)firstItemelse"!"+firstItem+"!" }.toUpperCase() println("Firstitemaftermodifications:'$modifiedFirstItem'")
with
一个非扩展函数:上下文对象作为参数传递,但是在lambda表达式内部,它可以作为接收者(this)使用。返回值是lambda表达式结果。
建议使用with来调用上下文对象上的函数,而不使用lambda表达式结果。在代码中,with可以理解为“对于这个对象,执行以下操作。”
valnumbers=mutableListOf("one","two","three") with(numbers){ println("'with'iscalledwithargument$this") println("Itcontains$sizeelements") }
with的另一个使用场景是引入一个辅助对象,其属性或函数将用于计算一个值。
valnumbers=mutableListOf("one","two","three") valfirstAndLast=with(numbers){ "Thefirstelementis${first()},"+ "thelastelementis${last()}" } println(firstAndLast)
run
上下文对象作为接收者(this)来访问。返回值是lambda表达式结果。
run和with做同样的事情,但是调用方式和let一样——作为上下文对象的扩展函数.
当lambda表达式同时包含对象初始化和返回值的计算时,run很有用。
valservice=MultiportService("https://example.kotlinlang.org",80) valresult=service.run{ port=8080 query(prepareRequest()+"toport$port") } //同样的代码如果用let()函数来写: valletResult=service.let{ it.port=8080 it.query(it.prepareRequest()+"toport${it.port}") }
除了在接收者对象上调用run之外,还可以将其用作非扩展函数。非扩展run可以使你在需要表达式的地方执行一个由多个语句组成的块。
valhexNumberRegex=run{ valdigits="0-9" valhexDigits="A-Fa-f" valsign="+-" Regex("[$sign]?[$digits$hexDigits]+") } for(matchinhexNumberRegex.findAll("+1234-FFFFnot-a-number")){ println(match.value) }
apply
上下文对象作为接收者(this)来访问。返回值是上下文对象本身。
对于不返回值且主要在接收者(this)对象的成员上运行的代码块使用apply。apply的常见情况是对象配置。这样的调用可以理解为“将以下赋值操作应用于对象”。
valadam=Person("Adam").apply{ age=32 city="London" } println(adam)
将接收者作为返回值,可以轻松地将apply包含到调用链中以进行更复杂的处理。
also
上下文对象作为lambda表达式的参数(it)来访问。返回值是上下文对象本身。
also对于执行一些将上下文对象作为参数的操作很有用。对于需要引用对象而不是其属性与函数的操作,或者不想屏蔽来自外部作用域的this引用时,请使用also。
当在代码中看到also时,可以将其理解为“并且用该对象执行以下操作”。
valnumbers=mutableListOf("one","two","three") numbers .also{println("Thelistelementsbeforeaddingnewone:$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作用域函数区别与使用场景内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。