Kotlin学习 - Kotlin中的标准函数let、with、run、apply、also

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表达式入参。

同样的是,withrun的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的区别

从上面表格看,withrun的内部上下文和返回值都是一样的,所以这两个使用起来有啥区别呢?

  • 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那么频繁。

参考文章
Kotlin中let、run、with、apply及also的差别

猜你喜欢

转载自blog.csdn.net/kongqwesd12/article/details/131293825