阅读kotlin官方文档空安全篇时看到其中一段代码如下
val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let { println(it) } // 输出 Kotlin 并忽略 null
}
没有看懂最后一行的item.let,于是搜索了一下let方法,发现let处于kotlin 包下的 Standard 中,其中与之相似的 run 、 apply 、also 和 with ,他们的功能大致都是将某值作为参数调用指定的函数块并返回其结果,那么他们的区别是什么?在经过我的学习查阅之后将其做个小结。
1 .run
public inline fun <R> run(block: ()-> R): R = block()
有2种用法
//用法1
功能:调用某对象的run函数块,返回值为函数块最后一行或者指定return表达式。
val a =run{
println("string-run1")
//"我是最后一行数据"//返回值是最后一行
return@run "我是被指定的数据"//指定返回类型
}
println("a ------- $a")
//用法2
功能:调用某对象的run函数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式。
val b ="string-run2".run {
println(this)
"我是最后一行数据"//返回值是最后一行
//return@run "我是被指定的数据"//指定返回类型
}
println("b ------- $b")
可以发现,不管是方法1还是方法2,run都可以以最后一行作为返回值或者指定返回数据及类型,唯一不同就是方法2中多了this关键字指代该对象
2 .let
public inline fun <T, R> T.let(block: (T)-> R): R = block(this)
功能:调用某对象的let函数,该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定return表达式
val c ="string-let".let {
println(it)//it指定该对象
110//在最后一行作为返回值
//return@let "我是被指定的返回数据"//指定返回类型
}
println("c ------- $c")
与run类似,let也可以将代码块中最后一行作为返回值及类型或者用注解指定返回值及类型,然而run方法2中拥有this关键字指代该对象,在let中我们使用it关键字指代该对象
3.also
public inline fun <T> T.also(block: (T)-> Unit): T { block(this); return this }
功能:调用某对象的also函数,该对象作为函数的参数。在函数块内可以通过 it 指代该对象。返回值为该对象自己。
val d ="string-also".also {
println(it)//it指代该对象
}
println("d ------- $d")//返回值是该对象自己
可以发现also与let相似,都具有it关键字指代该对象,但与run和let都不同的一点是:不可指定返回类型,返回值只能为该对象自己
4.apply
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
功能:调用某对象的apply函数,在函数块内可以通过 this 指代该对象。返回值为该对象自己。
val e ="string-apply".apply {
println(this)//this指代该对象
}
println("e ------- $e")//返回值是该对象自己
apply与also相似,其返回值只能为该对象自己,并且在代码块中拥有关键字this指代该对象自己
5.with
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
功能:with与之前几个方法略有不同,run,let,apply,also都是以扩展的形式存在的,而with不是,在with中,对象以参数的形式被传入with
//with
val f =with("string-with"){
println(this)
//120 //最后一行时作为返回值
return@with 999 //指定返回指
}
在函数块中拥有this指代该对象,返回值也可以被指定或是取代码块最后一行
用run比用with好?
通过上面的总结,我们发现run的第二种写法似乎与with没啥区别,那我选择使用run还是with?看下面代码
var str:String?="ABC"
str =null
val ss1 =str?.run {
println(str.length)
}
val ss2 =with(str){
//println(str.length) //str可能为空,编译出错
println(str?.length)//加上?通过编译
if(str!=null){
println(str.length)//判断是否为空通过编译
}
}
在第二部分(ss2)中可以发现在str为空时,我们在下面打印长度是不可行的,如果没有判空或者“?”编译器都不会让你通过。然而在第一部份(ss1)中我们将“?”移至对象str后,直接在run代码块前进行空判断,为空直接不进入代码块。相比于with的在内部判空效率肯定会高很多(想象在with块中判空前可能有大量无关代码)