Kotlin标准函数:
标准函数let
fun main() {
val student = Student("lucky", 19);
study(student)
}
fun study(student: Student?) {
study?.doHomework()
study?.readBooks()
}
在Kotlin学习 - 可空系统类型中,如果入参是可以为空的,那么函数中每次调用该对象函数都需要加?.
,代码编写太过繁琐。这个时候就可以结合使用?.
操作符和let
函数来对代码进行优化了,如下所示:
fun main() {
val student = Student("lucky", 19);
study(student)
}
fun study(student: Student?) {
student?.let { s ->
s.doHomework()
s.readBooks()
}
}
?.
操作符表示对象为空时什么都不做,对象不为空时就调用let
函数。之前做lamda表达式优化的时候讲(Kotlin学习 - 集合):当Lambda 表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替,因此上面代码可以优化下:
fun main() {
val student = Student("lucky", 19);
study(student)
}
fun study(student: Student?) {
student?.let {
it.doHomework()
it.readBooks()
}
}
let
除了可以帮我们简化非空判断,在多线程中还可以控制处理全局变量的判空问题。
var student: Student? = null
fun study() {
if (student !=null){
student.doHomework()
student.readBooks()
}
}
上面代码是会编译报错的,是因为全局变量的值随时都有可能被其他线程所修改,即使做了判空处理,仍然无法保证if
语句中的student
变量没有空指针风险。使用let
函数修改下:
var student: Student? = null
fun study() {
student?.let {
it.doHomework()
it.readBooks()
}
}
报错消失,判断student
变量不为空后,执行let中的语句,let可确保lambda语句执行的过程中student
变量一直不为空。
总结:let
可以帮我们简化非空判断,在多线程中还可以控制处理全局变量的判空问题。
标准函数with
fun main() {
val fruits = listOf("apple", "banana", "pear")
println(fruitPrint(fruits))
}
上面有个水果列表,需求是按固定的格式拼接起来,惯常实现方法如下:
fun fruitPrint(fruits: List<String>): String {
val fruitPrint = StringBuilder();
fruitPrint.append("My favorite fruits are : \n")
for (f in fruits) {
fruitPrint.append(f).append("\n")
}
fruitPrint.append("----- end -----")
return fruitPrint.toString()
}
//打印结果
My favorite fruits are :
apple
banana
pear
----- end -----
在拼接字符串的地方连续调用了多次StringBuilder
对象。针对这种场景我们可以用with
函数来让代码变得更加精简。
fun fruitPrint(fruits: List<String>): String {
return with(StringBuilder()) {
append("My favorite fruits are : \n")
for (f in fruits) {
append(f).append("\n")
}
append("----- end -----")
toString()
}
}
给with
函数的第一个参数传入了一个StringBuilder
对象,那么整个Lambda 表达式的上下文就会是这个StringBuilder
对象。于是我们在Lambda 表达式可以直接调用append()
和toString()
方法。Lambda 表达式的最后一行代码会作为with
函数的返回值返回。
总结:需要同一对象频繁操作的时候,可以使用with
函数精简代码。
标准函数run
run
函数的用法和with
函数是非常类似的,不过run
函数通常不会直接调用,而是要在某个对象的基础上调用。而且with
有两个入参,run
只有一个Lambda表达式入参。
同样的是,with
和run
的Lambda 表达式中的最后一行代码作为返回值返回。
上面例子用run
函数重写下:
fun fruitPrint(fruits: List<String>): String {
return StringBuilder().run {
append("My favorite fruits are : \n")
for (f in fruits) {
append(f).append("\n")
}
append("----- end -----")
toString()
}
}
标准函数apply
apply
函数和run
函数用法类似,都要在某个对象上调用,并且只接收一个Lambda 参数,也会在Lambda 表达式中提供调用对象的上下文,但是apply函数无法指定返回值,而是会自动返回调用对象本身。
fun fruitPrint(fruits: List<String>): String {
val sb = StringBuilder().apply {
append("My favorite fruits are : \n")
for (f in fruits) {
append(f).append("\n")
}
append("----- end -----")
}
return sb.toString()
}
由于apply
函数无法指定返回值,只能返回调用对象本身,因此无法直接返回,我们的返回值声明的是String类型,因此还需要转化下。
再来看个Android中的用法:
val intent = Intent(this,MainActivity::class.java).apply {
putExtra("data1","data1")
putExtra("data2",2)
putExtra("data3",true)
}
startActivity(intent)
启动MainActivity的时候,可以去配置Intent
中包含的参数。
标准函数also
also用法和apply用法比较像,看个例子:
fun studentInfoCheck(s: Student) {
s.also { it.name = "Lucky" }
println(s.name)
}
在内部使用it
来引用上下文,可以对对象进行一系列操作。返回值和apply
一样只能返回调用对象本身。
标准函数的区别
学习到这里是不是都蒙圈了,下面对比下几个函数的差异,看上去能清晰些。
标准函数 | 内部上下文 | 返回值 |
let | it | let块的最后一行代码 |
with | this(可省略) | let块的最后一行代码 |
run | this(可省略) | let块的最后一行代码 |
apply | this(可省略) | 调用对象本身 |
also | it | 调用对象本身 |
with & run的区别
从上面表格看,with
和run
的内部上下文和返回值都是一样的,所以这两个使用起来有啥区别呢?
-
with
有两个入参,run
只有一个Lambda表达式入参; - 空安全判断方式不同;
针对第二点我们来看看案例:
fun printStudentInfo() {
val s: Student? = null
with(s) {
this?.name = "Tom"
this?.age = 4
}
print(s)
}
上面是使用with
对空判断,使用的时候每次都需要再次判断非空,改成run
试试
fun printStudentInfo() {
val s: Student? = null
s?.run {
name = "Tom"
age = 4
}
print(s)
}
首先run
函数的调用省略了this
引用,在外层就进行了空安全检查,只有非空时才能进入函数块内对Student
进行操作。相比较with
来说,run
函数更加简便,空安全检查也没有with
那么频繁。