大家好,我是青空。今天给大家带来的是kotlin 作用域函数。
文末可领取大厂面试题
作用域函数是什么意思呢
通过编译器的手段增加一些操作符,使代码变得更简洁
所以,你不用它也完全可以实现相同的功能。
它提供了一个临时作用域,让对象执行代码块的 代码看起来更简洁
感受一下 作用域函数 带来的代码整洁
data class Person(var name: String, var age: Int, var city: String) {
fun moveTo(newCity: String) { city = newCity }
fun incrementAge() { age++ }
}
fun main() {
Person("Alice", 20, "Amsterdam").let {
println(it)
it.moveTo("London")
it.incrementAge()
println(it)
}
}
将main()函数的代码改成普通的方式
val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)
使用 let 之后,代码更显层次,更方便的寻找 Person alice的作用域(有效范围)
作用域函数的弊端是什么
显然代码整洁了
但是可读性却下降了。lambda过多就会导致修改起来很累。
要考虑到交接成本。后来者可能改不动你的代码
Kotlin有五个范围函数
-
let
-
run
-
with
-
apply
-
also
5个范围函数的区别
两个主要区别
-
上下文对象用this 还是 it
-
返回值
上下文对象用this 还是 it
-
this和it是作用域函数获取对象引用的 短名称
-
两者都提供相同的功能
-
使用this的情况
-
范围函数:run,with,apply 使用this
-
在大多数情况下,this访问接收器对象的成员时可以省略,从而使代码更短
比如这个例子就是省略了this
data class Person(var name: String, var age: Int = 0, var city: String = "")
fun main() {
val adam = Person("Adam").apply {
age = 20 // same as this.age = 20 or adam.age = 20
city = "London"
}
}
但是不加this,就不容易区分age、city是来自内部接收器Person 还是 外部成员或功能
大家看下不容易区分的例子
data class Person(var name: String, var age: Int = 0, var city: String = "")
var otherName = "wangxueming"
fun main() {
val adam = Person("Adam").apply {
age = 20 // same as this.age = 20 or adam.age = 20
city = "London"
otherName = "wxm"
}
println("age = ${adam.age}; city = ${adam.city}; otherName = ${otherName}")
}
这里的otherName你就不容易看出来到底是Person的变量还是外部变量
使用it的情况
-
范围函数:let,also使用it
-
it相较this更为简洁
-
但在范围域内this就不可用了
看范例
import kotlin.random.Random
fun writeToLog(message: String) {
println("INFO: $message")
}
fun main() {
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
//下面这行的this会报编译错误:'this' is not defined in this context
//writeToLog("getRandomInt() generated value $this")
}
}
val i = getRandomInt()
}
it在lambda中是支持自定义名称的
将上个范例中的getRandomInt(),从it改成value
fun getRandomInt(): Int {
return Random.nextInt(100).also { value -> {
writeToLog("getRandomInt() generated value $value")
writeToLog("getRandomInt() generated 222 value $value")
}
}
}
返回值
-
apply、also返回上下文对象
-
let、run、with返回lambda结果
-
返回 上下文对象
-
准确的是是对fun的链式调用
-
返回又是上下文对象
-
apply、also会返回 上下文对象
-
由于apply、also发起者是 上下文对象
-
这就是个链式调用嘛
-
链式调用就可以这么写了
fun main() {
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()
println(numberList)
}
-
进一步理解一下,in是上下文对象,out依旧是上下文对象
-
这说明 also和apply不对原先的操作造成任何影响
-
原来的是fun,添加了also或apply之后,返回的依旧是fun
-
also、apply可以理解为对原先 fun的补充逻辑处理
我们再看一遍RandomInt的例子
import kotlin.random.Random
fun writeToLog(message: String) {
println("INFO: $message")
}
fun main() {
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
val i = getRandomInt()
}
-
添加also之后,Random.nextInt(100)不受影响,只是额外打印了一行log
-
这就可以动态的 给fun添加一个原先fun处理完成后的逻辑
-
返回lambda表达式结果
-
let、run、with会返回 lambda表达式结果
-
这就提供了 将fun计算的结果传给下一个函数继续计算
-
这是对结果的链接调用
-
看个统计末尾 字母为e个数的范例
fun main() {
val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run {
add("four")
val Res1 = count { it.startsWith("f") }
println("Res1 = ${Res1}")
add("five")
val Res2 = count { it.startsWith("f") }
println("Res2 = ${Res2}")
println("${numbers}")
count { it.endsWith("e")&&it.startsWith("t") }
}
println("There are $countEndsWithE elements that end with e.")
}
输出结果为
Res1 = 1
Res2 = 2
[one, two, three, four, five]
There are 1 elements that end with e.
-
正如文章开头说的,作用于表达式 只是书写的简化
-
相当于是 多行 逻辑处理的整合
-
每一行代码都能依次执行,并及时的得到相应的结果
具体讲讲这5个范围函数的用法
很多时候这5个范围函数是可以互换的
同一个逻辑可以用不同的范围函数来实现
接下来讲讲 常见的使用方法
let
上下文对象是it
返回lambda结果
普通写法
fun main() {
val numbers = mutableListOf("one", "two", "three", "four", "five")
val resultList = numbers.map { it.length }.filter { it > 3 }
println(resultList)
}
用法1
重写部分使用了某变量的逻辑,显得更有代码结构层次感
改写普通写法
fun main() {
val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let {
println(it)
}
}
若代码块 仅包含一个it参数的单一函数
还可以用反射进行改写
fun main() {
val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let(::println)
}
用法2
搭配 ?. 进行非空判断
fun processNonNullString(str: String) {}
fun main() {
val str: String? = null
//processNonNullString(str) // compilation error: str can be null
val length = str?.let {
println("let() called on $it")
processNonNullString(it) // OK: 'it' is not null inside '?.let { }'
it.length
}
}
这里str为null,则let不会被执行。若str有值,则let代码块可以被执行
用法3
使用 it的名别 来提高代码可阅读性
fun main() {
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'")
}
这里的firstItem即it的别名。从字面上可以更清楚的制度这里的it是什么
with
上下文对象是this
返回lambda结果
with可以被解读为
请用这个object做以下的事情
用法1
用numbers去打印log
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
println("'with' is called with argument $this")
println("It contains $size elements")
}
用法2
调用其functions
fun main() {
val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
"The first element is ${this.first()}," +
" the last element is ${last()}"
}
println(firstAndLast)
}
这里会输出
The first element is one, the last element is three
run
上下文对象是this
返回lambda结果
独立运行
val hexNumberRegex = run {
val digits = "0-9"
val hexDigits = "A-Fa-f"
val sign = "+-"
Regex("[$sign]?[$digits$hexDigits]+")
}
可以看到没有 context对象
这里范例返回的就是Regex
像let一样
let用的是it,run用的是this
范例
class Person(var name: String, var height: Int) {
fun signIn(): String = "$name has sign in"
fun getHeight(height: String): String = "Height = '$height'"
}
fun main() {
val alice = Person("Alice", 188)
val result = alice.run {
this.name = "$name the Student"
println(signIn() + " to port " + getHeight("$height"))
"alice clone1 success" //输出最后一行的结果
}
// the same code written with let() function:
val result2 = alice.let {
it.name = "${it.name} the Student"
println(it.signIn() + " to port " + it.getHeight("${it.height}"))
"alice clone2 success" //输出最后一行的结果
}
println(result)
println(result2)
}
输出结果呢
Alice the Student has sign in to port Height = '188'
Alice the Student the Student has sign in to port Height = '188'
alice clone1 success
alice clone2 success
apply
上下文对象是this
返回 上下文对象
使用apply的代码块没有返回值,主要是操作上的接收器对象的成员
主要用来进行对象配置
范例
data class Person(var name: String, var age: Int = 0, var city: String = "")
fun main() {
val adam = Person("Adam").apply {
age = 32
city = "London"
}
}
also
上下文对象是it
返回 上下文对象
这可以理解为 我也要做什么什么
定义为附加功能最合适
顺便打个log吧
fun main() {
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
}
函数选择
功能 | 对象参考 | 返回值 | 是扩展功能 | 适用情况 |
---|---|---|---|---|
let | it | Lambda结果 | 是 | 仅支持在非null对象上使用;将 表达式 像变量一样引入到 作用域中 |
run | this | Lambda结果 | 是 | 对象配置和计算结果 |
run | – | Lambda结果 | 否:在没有上下文对象的情况下调用 | 需要表达式的运行语句 |
with | this | Lambda结果 | 否:将上下文对象作为参数。 | 对对象进行分组功能调用 |
apply | this | 上下文对象 | 是 | 对象配置 |
also | it | 上下文对象 | 是 | 附加功能 |
takeIf与takeUnless
takeIf
-
针对单个对象的过滤功能
-
匹配则返回该对象,不匹配返回null
takeUnless
-
与takeIf相反
-
匹配
注意
-
takeIf和takeUnless是过滤
-
返回的结果 是 原先的对象
-
这可以达到链式调用的效果!!!
-
takeIf和takeUnless可以和五个作用域函数 搭配使用
范例
fun main() {
val number = 6
val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
println("even: $evenOrNull, odd: $oddOrNull")
val number2 = 7
val evenOrNull2 = number2.takeIf { it % 2 == 0 }
val oddOrNull2 = number2.takeUnless { it % 2 == 0 }
println("even: $evenOrNull2, odd: $oddOrNull2")
}
可以 看出来takeIf与takeUnless的结果可能是null
最近又赶上跳槽的高峰期,好多粉丝,都问我有没有最新大厂面试题,我连日加班好多天,终于整理好了,1000+ 道,20多份大厂面试题大全,我会持续更新中!公众号回复【面试题】即可获取。