Kotlin基础知识(十)——带接受者的lambda:“with”与“apply”

337 阅读2分钟

一、“with”函数

  • 示例一:构建字母表
// 定义
fun alphabet(): String {
    val result = StringBuilder()
    for(letter in 'A' .. 'Z') {
        result.append(letter)
    }
    result.append("\nNow I know the alphabet!")
    return result.toString()
}

// 测试
>>> println(alphabet())
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Now I know the alphabet!

上述例子中,调用result实例上好几个不同的方法,而且每次调用都要重复result这个名称。若实例名比较长,就比较糟糕!

  • 使用***with***构造字母表
fun alphabet(): String {
    val stringBuilder = StringBuilder()
    // 指定接受者的值,你会调用它的方法
    return with(stringBuilder) {
        for(letter in 'A' .. 'Z') {
            // 通过显式的“this”来调用接受者值的方法
            this.append(letter)
        }
        // 省略“this”也可以调用方法
        append("\nNow I know the alphabet!")
        // 从lambda返回值
        this.toString()
    }
}

with结构看起来像是一种特殊的语法结构,但它实际上是一个接受两个参数的函数:这个例子中两个参数分别是stringBuilder和一个lambda。这里利用了把lambda放在括号外的约定,这样整个调用看起来就像是内建的语言功能。当然也可以把它写成*with(stringBuilder, { ... })***,但可读性就会差很多。

with函数把它的第一个参数转换成作为第二个参数传给它的lambda的接受者。可以显示地通过this引用来访问这个接受者。或者,按照惯例,可以省略this引用,不用任何限定符直接访问这个值的方法和属性。

  • 重构:使用with和一个表达式函数体来构建字母表
fun alphabet() = with(StringBuilder()) {
    for(letter in 'A' .. 'Z') {
        append(letter)
    }
    append("\nNow I know the alphabet!")
    toString()
}

现在这个函数只返回一个表达式,所以使用表达式函数体语法重写了它。可以创建一个新的StringBuilder实例直接当作实参传给这个函数,然后在lambda中不需要显示的this就可以引用这个实例。

注意with返回的值是执行lambda代码的结果,该结果就是lambda中的最后一个表达式(的值)**。

二、“apply”函数

apply*函数几乎和with函数一模一样,唯一的区别**apply始终会返回作为实参传递给它的对象*(换句话说,接受者对象)。

  • 使用apply构建字母表
fun alphabet() = StringBuilder().apply {
    for(letter in 'A' .. 'Z')
        append(letter)
    append("\nNow I know the alphabet!")
}.toString()

***apply被声明成一个扩展函数。它的接受者变成了作为实参的lambda的接受者。执行apply***的结果是StringBuilder,所以接下来你可以调用toString把它转换成String。

许多情况下***apply***都很有效:

  • 创建一个对象实例并需要用正确的方式初始化它的一些属性时。在Java中,这通常是通过另外一个单独的Builder对象来完成的;而在Kotlin中,可以在任何对象上使用***apply***,完成不需要任何来自自定义该对象的哭的特殊支持。
fun createViewWithCustomAttributes(context: Context) =
        TextView(context).apply { 
            text = "Sample Text"
            textSize = 20.0F
            setPadding(10, 0, 0, 0)
        }