再学一遍吧——关于kotlin那些基本的扩展函数

165 阅读4分钟

1. 讲啥的

kotlin的扩展函数很多,我这里指的基本的扩展函数,具体是 with(不是扩展函数)、let、run、apply、also这五个

2. 背景

最近写代码,被mentor评论道:有时间多看看with、let、run、apply、also这五个函数的作用特点😂。虽然自己都知道这五个函数,但具体哪个要用在什么情况上面,确实还是一知半解,现在都是also,apply一刀切。于是利用假期时间,学习一下

3. 正文

总体上来说,我将这五个函数,分为两部分。划分的依据是返回值,具体划分结果是: with、let、run为一组,apply、also为一组。因为从默认情况来看,with、let、run都是以闭包形式,将表达式的最后一行作为返回值,而apply、also是返回对象本身。下面将依次介绍

3.1 with

特点:将对象作为参数传入,函数内使用this指代对象(this可省略),最后一行作为返回值。

缺点:不能方便地判空

先来看个例子吧

class Person {
    var name: String ?= ""
    var age: Int ?= 0
    var gender: String ?= ""
}

fun main() {
    val person = Person()
    val personName = with(person) {
        name = "张三"
        age = 21
        gender = "男"
        name // 最后一行,作为返回值
    }

    println(personName)
}

但假如传入的对象为空对象,就有可能出现空指针异常了。除此之外,with是一个内联函数,而非扩展函数。

3.2 let

特点:是对象的扩展函数,函数内使用it指代对象(it不可省略),最后一行作为返回值

优点:可克服with的缺点,较为方便地判空。但it不能省

看个例子

class Person {
    var name: String ?= ""
    var age: Int ?= 0
    var gender: String ?= ""
}

fun main() {
    val person = Person()
    val personName = person?.let {
        it.name = "张三"
        it.age = 21
        it.gender = "男"
        it.name // 最后一行作为返回值
    }

    println(personName)
}

这里即使对象是空,也能较为方便地进行兜底。不太好的点是,it不能省略

3.3 run

特点: 是对象的扩展函数,函数内使用this指代对象(this可省略),最后一行作为返回值

优点: 结合了with和let的优点

看个例子

class Person {
    var name: String ?= ""
    var age: Int ?= 0
    var gender: String ?= ""
}

fun main() {
    val person = Person()
    val personName = person?.run {
        name = "张三"
        age = 21
        gender = "男"
        name // 最后一行作为返回值
    }

    println(personName)
}

可以看到,既可以较为方便地判空,又可以省略this

3.4 apply

特点: 是对象的扩展函数,函数内使用this指代对象(this可省略),返回对象本身

看个例子

class Person {
    var name: String ?= ""
    var age: Int ?= 0
    var gender: String ?= ""
}

fun main() {
    var person = Person()
    person = person?.apply {
        name = "张三"
        age = 21
        gender = "男"
    }

    println(person.toString())
}

我个人把它理解为run函数的特殊情况,因为两者都是使用this指代对象,this都可以省略,而且run也可以返回对象本身,比如上面的代码,也可以这样写

class Person {
    var name: String ?= ""
    var age: Int ?= 0
    var gender: String ?= ""
}

fun main() {
    var person = Person()
    person = person?.run {
        name = "张三"
        age = 21
        gender = "男"
        this // 最后一行返回对象本身
    }

    println(person.toString())
}
3.5 also

特点:是对象的扩展函数,函数内使用it指代对象(it不可省略),返回对象本身

看个例子

class Person {
    var name: String ?= ""
    var age: Int ?= 0
    var gender: String ?= ""
}

fun main() {
    var person = Person()
    person = person?.also {
        it.name = "张三"
        it.age = 21
        it.gender = "男"
    }

    println(person.toString())
}

我个人把它理解为let函数的特殊情况,因为两者都是使用it指代对象,it都不可省略,而且let也可以返回对象本身,比如上面的代码,也可以这样写

class Person {
    var name: String ?= ""
    var age: Int ?= 0
    var gender: String ?= ""
}

fun main() {
    var person = Person()
    person = person?.let {
        it.name = "张三"
        it.age = 21
        it.gender = "男"
        it
    }

    println(person.toString())
}

4. 总结

从主观上来总结,apply和also用的最多,run和let用的次之,with用的非常非常少。

至于apply和also的区分:当需要给对象内的属性赋值的时候,用apply更合理,当需要利用对象的属性的时候,用also更合理。run和let也是一样的道理。看个例子

class Person {
    var name: String ?= ""
    var age: Int ?= 0
    var gender: String ?= ""
}

fun main() {
    var person = Person()
    person = person?.apply {
        name = "张三"
        age = 21
        gender = "男"
    }

    println(person.toString())
}

这种就是对name、age、gender这些属性赋值,用apply更合适。而假如要用到这些属性的话,比如

fun main() {
    var person = Person()
    person = person?.also {
        println(it.name)
        doSomething(it.age, it.gender)
    }
}

那就是also更合适了。这些都是基于本人的小经验总结的。如有错误之处,还请各位批评指正

最后,来个表总结下

函数名返回值函数内指代对象
with函数最后一行this(可省略)
let函数最后一行it(不可省略)
run函数最后一行this(可省略)
apply对象本身this(可省略)
also对象本身it(不可省略)

其中,除with外,其余4个均为扩展函数。