Kotlin 里面的run,let,also,apply 就像图片中的木头铲子一样非常相似,有些时候我们不太清楚实战中应该使用哪一个标准函数,下面我们举一些例子来说明它们的区别。
咱们先看 run{} 标准函数最简单的一个用法:
fun world() {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) //I am sad
}
在 world 这个函数里面,你可以有一个单独的范围,重新定义mood。
来一个比较抽象的解释:你在这个世界每天要面对烦心的事情,心情比较沮丧(sad),但是你只要回到家里没有外界的干扰,心情就很美丽(happy),run {} 就好比你的家一样,是隔离外界的一个范围。
但是这个范围貌似没有太大的卵用,除了这个范围,还有没有其他的好处嘞?
//通常写法
if (first) {
view1.show()
} else {
view2.show()
}
//run 写法
run {
if(first) view1 else view2
} .show()
run {} 函数的写法不需要我们调用两次show()方法,代码量也有减少
我用下面这3个属性来区分这些标准函数它们之间的差别
webview.settings.javaScriptEnabled = true
webview.settings.databaseEnabled = true
// 效果一样
webview.settings.run {
javaScriptEnabled = true
databaseEnabled = true
}
看上面的代码,一个是普通的写法,一个是用 run{} 的写法,那run{}的好处是什么呢?
咱们假设 webview.settings 有可能为 null,那我们的写法就是这样的:
webview.settings?.javaScriptEnabled = true
webview.settings?.databaseEnabled = true
// Nice.
webview.settings?.run {
javaScriptEnabled = true
databaseEnabled = true
}
run{} 的写法可以让我们在调用方法之前就判断 webview.settings 是否为 null,而普通的写法则需要两次判空
下面的代码 run{} 和 let{} 他们范围里面的参数是不一样的,一个是this,一个是it
stringVariable?.run {
println("The length of this String is $length")
}
// 打印相同的内容
stringVariable?.let {
println("The length of this String is ${it.length}")
}
从上面看来好像 run{} 更好一点,因为代码量上少了那么一丢丢,但其实 let{} 也有自己的优点:
it 只是他的参数的默认名字,他还可以自定义参数的名称。
stringVariable?.let {
nonNullString ->
println("The non null string is $nonNullString")
}
下面我们来看一下 let{} 和 also{} ,如果单纯从函数的范围和参数来看,他们的效果是一样的
stringVariable?.let {
println("The length of this String is ${it.length}")
}
//效果是一样的
stringVariable?.also {
println("The length of this String is ${it.length}")
}
let{} 和 also{} 的区别在于他们的返回值是不一样的,let{} 的返回值是大括号里面的最后一行内容,also{} 返回的是它自己也就是this
val original = "abc"
original.let {
println("最开始的字符串是 $it") // "abc"
it.reversed() // it.reversed()会作为参数传给下一个 let
}.let {
println("反转后的字符串是 $it") // "cba"
it.length // it.length 会作为参数传给下一个 let
}.let {
println("字符串的长度是 $it") // 3
}
//同样使用 also 会打印不一样的结果
original.also {
println("最开始的字符串是 $it") // "abc"
it.reversed()// 虽然调用的反转,但传给下一个 also 的是 original 而不是 it.reversed()。
}.also {
println("所以这里输出的还是 $it") // "abc"
it.length // 这里调用了 length 但传给下一个 also 的还是 original 而不是 it.length。
}.also {
println("所以这里输出的还是 $it") // "abc"
}
//那如果你非要使用 also 达到同样的效果的话,那应该这样写
original.also {
println("最开始的字符串是 $it") // "abc"
}.also {
println("反转后的字符串是 ${it.reversed()}") // "cba"
}.also {
println("字符串的长度是 ${it.length}") // 3
}
还有最后一个 apply{} 对应上面的3个属性就是:
// 普通写法
fun createIntent(username: String, password: String): Intent {
val intent = Intent()
intent.putString("username", username)
intent.putString("password", password)
return intent
}
// apply 的写法
fun createIntent(username: String, password: String) =
Intent().apply { putExtra("username", username) }
.apply { putExtra("password", password) }
在实战中使用标准函数,很多时候都能达到惊人的效果,不仅代码量少,链式代码使可读性大大提高。
// 普通写法
fun makeDir(path: String): File {
val result = File(path)
result.mkdirs()
return result
}
// 使用标准函数的写法
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }
基于上面的三个属性,我们总结一下,怎样在实战中选择正确的那一个函数?
下面是一段伪代码,希望它能帮到你。
if (is需要返回自己?){
//是 return this
when (范围内的参数){
it -> {
使用 also {}
}
this -> {
使用 apply {}
}
}
} else {
//否,return other
when (范围内的参数){
it -> {
使用 let {}
}
this -> {
使用 run {}
}
}
}