Android Kotlin中apply、also、let、run、with的使用

apply

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 * 
 * this作为接收器,返回this
 */
@kotlin.internal.InlineOnly
public inline fun  T.apply(block: T.() -> Unit): T { ... }
使用场景

A:对象的初始化

val person = Person().apply {
    name = "Android"
    age = 20
}
println(person)

上面的代码也可以写的稍微复杂一点:

val person = Person().apply {
    name = "Android"
}.apply {
    age = 20
}
println(person)

also也可以实现同样功能:

val person = Person().also {
	it.name = "Android"
    it.age = 20
}

显然apply有一个很大的优势:没有必要使用it作为限定符,因为上下文对象this可以省略。使用also也有“好处”:it非必须名字,也就是我们可以自定义名字,“增加代码的可读性”。参考文档

BBuilder样式

data class FooBar(var a: Int = 0, var b: String? = null) {
    fun first(aArg: Int): FooBar = apply { a = aArg }
    fun second(bArg: String): FooBar = apply { b = bArg }
}

fun main(args: Array) {
    val bar = FooBar().first(10).second("foobarValue")
    println(bar)
}

also

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 * 
 * this作为参数,返回this
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun  T.also(block: (T) -> Unit): T { ... }

also看起来像let,除了它返回接收器T作为其结果。

使用场景

A:在代码块中接收器没有使用,变量赋值,并且打印log

val name = "Android".also {
    Log.e("name", "名字是$it")
}

这种情况和使用let一样的效果:

val name = "Android".let {
    Log.e("name", "名字是$it")
}

B:对象的初始化

val person: Person = Person().also {
   	it.name = "Android"
    it.age = 20
}

let

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 * 
 * this作为参数,返回it
 */
@kotlin.internal.InlineOnly
public inline fun  T.let(block: (T) -> R): R { ... }
使用场景

A:替代if (object != null) 语句

val len = string?.let {
    println("get length of $it")
    // 直接使用,不用it?.lenght,也不会遇到NullPointerException
    it.length
} ?: 0
println("Length of $text is $len")

或者

string?.let {
    println("get length of $it")
    it.length
}

B:限制变量使用范围,在let里面改变了其值后,外面的也会跟着改变

val str = "Android".let {
    println("str = $it")
    "Kotlin"
}
// 这个str的值其实是“Kotlin”,不是原始的值“Android”
println(str)

如果想要继续使用原始值,可以使用also代替let,这样就不会修改原始值。

val str = "Android".also {
	println("str = $it")
	it.reversed() // 取反
}
// 还是原始值“Android”
println(str)

with

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 * 
 * 使用指定的接收器作为其接收器,返回it
 */
@kotlin.internal.InlineOnly
public inline fun  with(receiver: T, block: T.() -> R): R { ... }
使用场景

A:在限定范围内处理一个对象

val s: String = with(StringBuilder("init")) {
    append("some").append("thing")
    println("current value: $this")
    toString()
}
println(s)

效果上可以使用let替代:

val s: String = StringBuilder("init").let {
    it.append("some")
    it.append("thing")
    println("current value: $it")
    it.toString()
}
println(s)

在Android常用的地方是在Adapter中,常规写法:

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.nameTextView.text = mGoodList[position].name
        holder.specTextView.text = mGoodList[position].spec
        holder.priceTextView.text = mGoodList[position].price
        holder.numberTextView.text = mGoodList[position].count
        holder.itemView.tag = position
    }

使用with函数

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        with(mGiftCommodityList[position]) {
            holder.nameTextView.text = name
            holder.specTextView.text = spec
            holder.priceTextView.text = price
            holder.numberTextView.text = count
            holder.itemView.tag = position
        }
    }

B:使用类的成员扩展函数

object Foo {
    fun printString(str: String){
        println(str)
    }
}

fun main(args: Array) {
	// 只能在Foo上下文中调用
    with(Foo) {
        printString("Kotlin")
    }
}

如果要对特定扩展函数进行有意义的分组,则特别推荐使用此策略。

run

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 * 
 * this作为接收器,返回it
 */
@kotlin.internal.InlineOnly
public inline fun  T.run(block: T.() -> R): R { ... }

let的替代方法。

使用场景

A:替代if (object != null) 语句

val len = string?.run {
    println("get length of $this")
    length
} ?: 0
println("Length of $text is $len")

或者

string?.run {
    println("get length of $this")
    length
}

对比

返回本身 返回任意值(最后一个表达式为返回值)
it also let
this apply run、with

letrun以及with存在返回值都有一些隐藏的提示:
Android Kotlin中apply、also、let、run、with的使用_第1张图片

Android Kotlin中apply、also、let、run、with的使用_第2张图片
run和with之间的对比

实现相同的功能:

// with可以
with(webview.settings) {
    javaScriptEnabled = true
    databaseEnabled = true
}
// run也没问题
webview.settings.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

但是当webview.setting可能为null时,我们再对比一下:

// with看上就不是很好
with(webview.settings) {
      this?.javaScriptEnabled = true
      this?.databaseEnabled = true
}

// run没问题
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

所以在此类情况(当前对象存在可为null)建议使用run扩展函数。

run可以进行链式编写:

val str = "Android".run {
    println("str = $this")
    "Kotlin"
}.run {
    println("str = $this")
    "Java"
}

原因是run函数是扩展函数,它还有一个T类型的隐式参数,因此参数类型是相同的。功能的主体也实际上是相同的。参考文档

返回this和其他任意值比较
// also
val name = "Android".also {
    Log.e("name", "名字是$it")
}

// let
val name = "Android".let {
    Log.e("name", "名字是$it")
}

上述两种写法效果是一样的,但是T.also返回的是T本身,而T.let返回的是任意类型的值。这样两者对链式函数的编写就会有不同的功能:T.let可以对不同的对象进行操作,而T.also可以对同一个对象进行处理。

val originalString = "Kotlin"
originalString.let {
    println("originalString is $it") // "abc"
    it.reversed() 
}.let {
    println("reverseString is $it") // "cba"
    it.length 
}.let {
    println("length is $it") // 3
}

// 同一个值,同样的结构使用also,就不会得到我们想要的结果
originalString.also {
    println("originalString is $it") // "abc"
    it.reversed() // 取反
}.also {
    println("reverseString is ${it}") // "abc"
    it.length  
}.also {
    println("length is ${it}") // "abc"
}

// 为了实现同样的效果进行修改 
original.also {
    println("originalString is $it") // "abc"
}.also {
    println("reverseString is ${it.reversed()}") // "cba"
}.also {
    println("length is ${it.length}") // 3
}

两种不同的功能,根据自己的使用场景进行选择,例如:有一个成绩单的List

  • 需要筛选出男生的展示、或者筛选出成绩小于60的、甚至筛选分数是满分100的分别作为一个List。这些都是对原始数据List进行操作,所以建议使用T.also链式函数。
  • 需要筛选出男生中分数小于60的。并且上课是不是打瞌睡的人作为一个List,这种情况就是在男生的基础上再次筛选分数低于60的,并且还打瞌睡,所以建议使用T.let链式函数。
  • 需要筛选出男生中满分100的,或者是低于60的分别作为一个LIst,这种是首先在男生的基础上,然后再分别筛选,这就需要T.letT.also组合了。

组合的例子:

// 普通编写
fun makeDir(path: String): File  {
    val result = File(path)
    result.mkdirs()
    return result
}
// 组合链式编写
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }
个人总结例子
// 可变list
val personList = mutableListOf()
// age大于50的人数
var count = 0
// add一个第一个对象【apply】
personList.add(Person().apply {
    name = "Kotlin"
    age = 60
    myCar = Car().apply {
        name = "宝马"
        price = "100万"
        number = "12345678"
        engine = Engine()
    }
})

// add第二个对象【apply】
personList.add(Person().apply {
    name = "Kotlin"
    age = 30
    myCar = Car().apply {
        name = "宝马"
        price = "100万"
        number = "12345678"
        engine = Engine()
    }
})

// for循环【forEach】
personList.forEach {
    // 判断当前person对象是否为null【let】
    it.myCar?.let { car ->
        // 修改当前person的car的engine信息【with(在限定范围内处理对象)】
        with(car.engine) {
            type = "宝马123"
            date = "2019.9.9"
            producer = "china"
        }
    }

    // 判断person的age是否大于50,返回人数【run】
    count = it.run {
        if (age > 50) count + 1 else count
    }
    // 最后没有做任何处理只是打印log【also】
}.also {
    Log.e("Kotlin", personList.toString())
}

参考文档

作用
概念
对比

你可能感兴趣的:(Kotlin,apply,also,let,run,with)