Kotlin - 作用域函数(apply、also、run、with、let)
一、作用域函数1.1 标准库中的作用域函数在 Standard.kt 标准库中提供了一些便捷的内置高阶函数可以帮助我们写出更简洁高效的代码。这些函数都是在一个对象上执行一个代码块形成一个临时作用域可以访问该对象而无需其名称不同的是这个对象在代码块中如何使用以及整个表达式的返回值是什么。由于是 inline 内联函数优化了调用函数出入栈的性能开销。参数T.()即 this直接使用调用者对象 public 的类成员配置自身。T即 it主要是将调用者对象当作参数使用额外做事也可以使用调用者对象 public 的类成员。返回值T即返回调用者对象本身可基于原始对象进行链式调用。R即返回Lambda最后一行的值如果不是一个具体值就是返回Unit可基于新值进行链式调用。说明使用场景applypublic inline fun T T.apply(block: T.() - Unit): T调用该对象的类成员返回该对象。最后还是返回该对象一系列调用该对象的类成员因此是对该对象进行配置。alsopublic inline fun T T.also(block: (T) - Unit): T将该对象当做参数使用返回该对象。最后还是返回该对象一系列将该对象当做参数传参使用也可以调用类成员因此是插入副作用进行附加操作。letpublic inline fun T, R T.let(block: (T) - R): R将该对象当做参数使用返回Lambda的值。①计算出新值或不需要返回值主要是一系列将该对象当作参数传参使用也可以调用类成员。②对可空类型对象的多次调用做统一的非空判断。withpublic inline fun T, R with(receiver: T, block: T.() - R): R调用该对象的类成员返回Lambda的值。不是扩展函数。效果和 run 一样由于不是扩展函数需要传入已有对象后使用。runpublic inline fun T, R T.run(block: T.() - R): R调用该对象的类成员返回Lambda的值。计算出新值或不需要返回值一系列调用该对象的类成员。public inline fun R run(block: () - R): R执行若干代码返回Lambda的值。不是扩展函数。计算出新值或不需要返回值聚合一系列代码调用方便阅读。1.1.1 apply一系列调用类成员。val student Student() //未使用每次都要使用变量名调用成员 student.id 123 student.name 张三 student.study() student.rest() //使用后聚合调用方便阅读直接调用类成员 student.apply { id 123456 name 李四 study() rest() }1.1.2 also一系列传参使用也可以调用类成员。val student Student() //未使用(每次都要用实例名调用针对该对象的一系列操作挤在一起) student.setname(张三) growingUp(student.age) student.study() setLeader(student) //使用后聚合调用方便阅读主要是传参使用也可以调用类成员 student.also { growingUp(it.age) setLeader(it) it.setname(张三) it.study() }1.1.3 let聚合调用。val student Student() //未使用(每次都要用实例名调用) student.setname(张三) growingUp(student.age) student.study() setLeader(student) //使用后聚合调用便于阅读 student.let { it.setname(张三) it.study() growingUp(it.age) setLeader(it) }统一非空判断。//未使用(每次都要使用安全调用符) student?.setName(张三) student?.setAge(18) student?.study() //使用后只需要使用一次安全调用符 student?.let { it.setName(张三) it.setAge(18) it.study() }提供为空默认值。val str: String? null val length ser?.let { it.length } ?: 31.1.4 with效果同 run不是扩展函数需要传入对象后使用。with(recyclerView) { layoutManager LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) adapter mAdapter }val student Student(张三, 18) val str with(student) { 名字$name 年龄$age //调用student类成员并返回新值 } print(str) //打印名字张三年龄181.1.5 run一系列调用类成员。val str Student(张三, 18).run { 名字$name 年龄$age //调用student类成员并返回一个String }聚合一段代码调用num?.let { println(The number is $it) } ?: run { println(The number is null) } run { println(1) println(1) }1.2 自定义作用域函数在 Compose 中对于作用域的应用特别多。详见1.3 避免坏味道作用域函数可以在特定上下文中执行代码块让代码更流畅。但过度或不恰当的嵌套会写出连作者自己一个月后都看不懂的代码。风险影响可读性灾难嵌套的作用域函数会产生多个隐式的 this 和 it你需要时刻追踪它们指向哪个对象。调试困难你很难在这些 lambda 表达式中设置断点或者一步步地跟踪代码执行流程。逻辑模糊复杂的嵌套掩盖了代码的真实意图。优雅处理约定作用域函数的嵌套不应超过两层。 如果超过两层请重构成独立的函数或使用更简单的表达方式。代码是写给人读的其次才是给机器执行的。回归简单如果一段代码用作用域函数写出来很难读那就把它“压平”使用普通的变量和 if 判断。清晰永远比所谓的“简洁”更重要。二、其他标准库函数2.1 repeat原理将 action 函数执行 times 次。第一次的索引为0。public inline fun repeat(times: Int, action: (Int) - Unit) {contract { callsInPlace(action) }for (index in 0 until times) {action(index)}}repeat(6) { print(A$it,) //打印A0,A1,A2,A3,A4,A5, }2.2 takeIf说明lambda 返回 true 则返回调用者对象否则返回 null。场景判断是否满足条件再决定是否赋值或者执行操作。和 if 相似作用于实例上避免了临时变量赋值的麻烦。public inline fun T T.takeIf(predicate: (T) - Boolean): T? {contract {callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)}return if (predicate(this)) this else null}val num: Int 3 println(num.takeIf { it 5 }.toString()) //打印null val str hello println(str.takeIf { it.length 5 } ?: default) //打印default2.3 takeUnless原理功能和 takeIf 相反predicate 返回 false 则返回调用者对象否则返回 null。public inline fun T T.takeUnless(predicate: (T) - Boolean): T? {contract {callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)}return if (!predicate(this)) this else null}val num: Int 3 println(num.takeUnless { it 5 }.toString()) //打印52.4 TODO用来标记待办会抛异常可以指定异常原因。(这个不是 //TODO)public inline fun TODO(): Nothing throw NotImplementedError()public inline fun TODO(reason: String): Nothing throw NotImplementedError(An operation is not implemented: $reason)