【Kotlin篇】作用域函数(Scope Functions)解析

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」

作用域函数用途

首先直接上官方文档:

The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an object with a lambda expression provided, it forms a temporary scope. In this scope, you can access the object without its name. Such functions are called scope functions.

可见所谓作用域函数,其实就是创建一个lambda表达式(作用域),在作用域内执行一个代码块。这种方式可以使代码量缩短,整个代码块也会更简洁易懂,不同的scope function的主要区别在于:

  1. 引用上下文对象的方式(this还是 it);
  2. 返回值的类型(上下文对象还是Lambda表达式结果)。

下面用一个例子来说明不同的作用域函数的实际区别:

apply

inline fun <T> T.apply(block: T.() -> Unit): T 
复制代码

apply函数通过lambda表达式的接收者this来引用上下文并返回上下文对象。示例代码:

    fun useApply() {
        val person = Person(name = "arctos", age = 29)

        // 1 year later...
        person.apply {
            age = 30	// 等同于this.age = 30
        }
        println(person) // 这里age = 30
    }
复制代码

apply可用于“将以下赋值操作应用于对象”,也就是作用于上下文对象内部的赋值操作来配置对象,比如这里设置person.age的值。

also

inline fun <T> T.also(block: (T) -> Unit): T
复制代码

also函数通过lambda表达式的参数it来引用上下文并返回上下文对象。示例代码:

    fun useAlso() {
        val person = Person(name = "arctos", age = 29)

        // 1 year later...
        person.also {
            it.age = 30
            playWith(it)
        }
        println(person) // 这里age = 30
    }
复制代码

also可用于*“并且用该对象执行以下操作”*,也就是执行一些将上下文对象作为参数的操作,比如这里使用的playWith(person)方法。

run

inline fun <R> run(block: () -> R): R
复制代码

run函数通过lambda表达式的接收者this来引用上下文并Lambda表达式结果。示例代码:

    fun useRun() {
        val person = Person(name = "arctos", age = 29)

        // 1 year later...
        val updatedAge = person.run {
            age = 30	// 等同于this.age = 30
            age * 10  // 返回一个计算的结果
        }
        println(updatedAge) // 这里updatedAge = 300
    }
复制代码

run函数还可以使用非扩展函数的方式执行一个由多个语句组成的代码块,例如:

    fun useRun2() {
        val person = Person(name = "arctos", age = 29)

        // 1 year later...
        run {
            person.age = 30
            println(person.age)
        }
    }
复制代码

run可用于*”当 lambda 表达式同时包含对象初始化和返回值的计算时”“对象初始化”*也就是类似apply上下文对象内部的赋值操作,但run同时可以返回经过计算的表达式结果,类似于let

let

inline fun <T, R> T.let(block: (T) -> R): R
复制代码

let函数通过lambda表达式的参数it来引用上下文并Lambda表达式结果。示例代码:

    fun useLet() {
        val someone: Person? = null
        
        someone?.let {
            it.name = "arctos"  // 仅当someone不为空时执行代码块
        }

        val person = Person(name = "arctos", age = 29)

        // 1 year later and I'm moving...
        someone?.let { arctos ->
            arctos.age = 30
            arctos.address = "new address"
        }
    }
复制代码

let的常用情况包括:1. 非空值执行代码块;2. 引入作用域受限的局部变量,也就是非空检查it重命名来替换 lambda 表达式参数以提高代码的可读性。

with

inline fun <T, R> with(receiver: T, block: T.() -> R): R
复制代码

with函数和run函数相似,也是通过lambda表达式的接收者this来引用上下文并Lambda表达式结果。但其区别在于with函数是一个非扩展函数,它把上下文对象作为一个参数进行调用。示例代码:

    fun useWith() {
        val person = Person(name = "arctos", age = 29)

        // 1 year later...
        val personInfo = with(person) {
            age = 30
            PersonInfo(name, age, address) // lambda表达式结果为创建的PersonInfo对象
        }
        println(personInfo.age) // 这里age = 30
    }
复制代码

with可用于*“对于这个对象,执行以下操作”*,也就是关于对象的一组操作, 比如这里创建PersonInfo对象并返回,另外因为with是非扩展函数,所以其不支持上下文对象的非空检查

总结

总结成一张表格来说明作用域函数的区别和选择偏好:

上下文对象引用方式 返回值类型 使用场景
apply Lambda接受者this 上下文对象 上下文对象内部的赋值操作
also Lambda参数it 上下文对象 将上下文对象作为参数的操作
run(扩展函数) Lambda接受者this Lambda表达式结果 上下文对象内部的赋值操作 + 计算结果
run(非扩展函数) —- Lambda表达式结果 在需要表达式的地方运行语句
let Lambda参数it Lambda表达式结果 非空检查;lambda参数it重命名
with Lambda接受者this Lambda表达式结果 非空对象的一组操作

最后说一下,个人认为这些作用域函数其实还是细微的使用场景的区别,很多时候它们之间也都是可以强行通用的,只是使用更合适的函数可以让你的代码更精炼,易读。这也是Kotlin所追求的一种更为简洁优雅的代码风格。

示例代码

KotlinScopeFunctions.kt

参考文章

Kotlin Scope Funtion官方文档

差异化分析,let,run,with,apply及also

Kotlin Cheatsheet: Scope Functions (let, run, apply, also, with)

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享