ZetCode

Kotlin 接收者关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的接收者概念支持强大的 DSL 创建和扩展函数。接收者关键字允许在对象的上下文中调用函数。本教程通过实际示例深入探讨了接收者。

基本定义

Kotlin 中的接收者是调用函数或属性的对象。this 关键字在其范围内引用接收者。带有接收者的函数字面量允许在隐式对象上调用方法。

扩展函数

扩展函数是接收者的最简单形式。它们无需继承即可向现有类添加功能。接收者是正在扩展的对象。

ExtensionFunction.kt
package com.zetcode

fun String.addExclamation(): String {
    return "$this!"
}

fun main() {
    val greeting = "Hello"
    println(greeting.addExclamation()) // Output: Hello!
}

这里,String 是接收者类型。在函数内部,this 指的是字符串实例。我们可以像在任何 String 上一样调用它。

带有接收者的函数字面量

Lambda 表达式可以有接收者,从而实现类似 DSL 的语法。接收者在 lambda 内部作为 this 可用。

ReceiverLambda.kt
package com.zetcode

class Html {
    fun body() = println("Creating body")
    fun div() = println("Adding div")
}

fun html(init: Html.() -> Unit): Html {
    val html = Html()
    html.init()
    return html
}

fun main() {
    html {
        body()
        div()
    }
}

html 函数接受一个带有 Html 接收者的 lambda。在 lambda 内部,我们可以直接调用 Html 方法。这种模式在 DSL 构造中很常见。

作用域函数

Kotlin 的标准作用域函数(letrun 等)广泛使用接收者。它们提供了不同的方式来访问接收者对象。

ScopeFunctions.kt
package com.zetcode

data class Person(var name: String, var age: Int)

fun main() {
    val person = Person("Alice", 25)
    
    person.run {
        println("Name: $name, Age: $age") // this is implicit
    }
    
    person.let {
        println("Name: ${it.name}, Age: ${it.age}") // explicit it
    }
}

run 使用接收者作为 this,而 let 使用 it。两者都提供了以不同风格访问对象的方式。根据可读性需求进行选择。

DSL 构建

接收者通过嵌套上下文实现干净的 DSL 语法。每个嵌套块都有自己的接收者,从而实现分层结构。

HtmlDsl.kt
package com.zetcode

class Table {
    fun tr(init: Tr.() -> Unit) {
        Tr().init()
    }
}

class Tr {
    fun td(content: String) {
        println("<td>$content</td>")
    }
}

fun table(init: Table.() -> Unit): Table {
    val table = Table()
    table.init()
    return table
}

fun main() {
    table {
        tr {
            td("Data 1")
            td("Data 2")
        }
    }
}

此 HTML DSL 示例显示了嵌套接收者。table 接收者启用 tr 调用,而 tr 接收者启用 td。每个块都在其特定上下文中运行。

高阶函数中的接收者

高阶函数可以将带有接收者的函数作为参数。这允许灵活的行为注入,同时保持上下文。

HigherOrderReceiver.kt
package com.zetcode

class Calculator {
    var result = 0
    
    fun add(value: Int) { result += value }
    fun subtract(value: Int) { result -= value }
}

fun calculate(operations: Calculator.() -> Unit): Int {
    val calculator = Calculator()
    calculator.operations()
    return calculator.result
}

fun main() {
    val result = calculate {
        add(5)
        subtract(3)
        add(10)
    }
    println(result) // Output: 12
}

calculate 函数接受一个带有 Calculator 接收者的 lambda。在 lambda 内部,我们可以直接调用 Calculator 方法。该函数在所有操作完成后返回最终结果。

多个接收者

Kotlin 允许使用嵌套函数类型指定多个接收者。这支持具有多个上下文的复杂 DSL。

MultipleReceivers.kt
package com.zetcode

class Database {
    fun query(sql: String) = println("Executing: $sql")
}

class Logger {
    fun log(message: String) = println("LOG: $message")
}

fun withDatabaseAndLogger(action: Database.(Logger) -> Unit) {
    val db = Database()
    val logger = Logger()
    db.action(logger)
}

fun main() {
    withDatabaseAndLogger { logger ->
        logger.log("Starting query")
        query("SELECT * FROM users")
        logger.log("Query completed")
    }
}

此示例显示了一个带有两个接收者的函数:Database 作为主接收者,Logger 作为参数。lambda 可以访问这两个上下文,从而实现组件之间的协调操作。

泛型函数中的接收者

泛型函数可以使用接收者对各种类型执行类型安全操作,同时保持上下文。

GenericReceiver.kt
package com.zetcode

fun <T> T.applyIf(condition: Boolean, block: T.() -> T): T {
    return if (condition) block() else this
}

fun main() {
    val number = 10
    val result = number.applyIf(number > 5) {
        this * 2
    }
    println(result) // Output: 20
    
    val text = "Hello"
    val modified = text.applyIf(text.length > 3) {
        uppercase()
    }
    println(modified) // Output: HELLO
}

泛型 applyIf 函数适用于任何类型 T。接收者块可以执行特定于类型的操作。条件决定是否应用该块。

接收者的最佳实践

来源

Kotlin 带有接收者的函数字面量

本教程深入探讨了 Kotlin 的接收者概念,展示了从扩展函数到 DSL 构造的各种应用。接收者可以实现强大且具有上下文感知能力的代码,同时保持可读性和类型安全。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我写了 1400 多篇文章和 8 本电子书。我拥有超过八年的编程教学经验。

列出 所有 Kotlin 教程