Loading... # 何为 DSL? Domain-Specific Language。 DSL。 特定领域语言。 本意是专为某一特定领域设立的专用语言。 就像 HTML,用于声明 DOM 树。 就像 JSX,用于以 JavaScript 的方式描述 DOM 树。 实际上 JSX 就是专门开发用于方便实用 JavaScript 描述 DOM 树的语言。 而 JSX 实际上是在 JavaScript 上做了一些额外增强。 而有一些语言本身就足够灵活,能让我们基于语言特性本身,实现一些 DSL 能力。 就像 Kotlin。 ``` Kotlin html { head { title("这是页面标题") } body { div("这是页面内容") } } ``` 就这样,我们可以完全基于 Kotlin 的语言特性,设计出一组专门用于描述 DOM 结构的代码。 这就是基于 Kotlin 语言特性设计的特定领域语言。 Kotlin DSL。 # Kotlin 是怎么实现的 Kotlin 实现这个功能,依赖 Kotlin 两个及其核心的特性。 1. Kotlin 允许对类进行扩展函数。 2. Kotlin 允许将函数参数末尾的 lambda 参数,使其括号移出函数调用括号。 ## 扩展函数 Kotlin 支持对某个类提供扩展函数封装,在扩展函数方法体中,this 会被指向被扩展的类的实例。 ``` fun String.print(){ println(this) } ``` 此时,print 函数中的 this 是被扩展的 String。 他的 实例 是从何而来呢? 是从你实际调用的这个 print 函数处来的。 ``` "123".print() ``` 此时上面扩展函数的 this 就是这里的 "123"。 这就是 Kotlin 的扩展函数。 ## Lambda 这里有两个特性。 1. Kotlin 允许位于函数末尾的 lambda 参数提出调用括号。 2. Kotlin 允许在 lambda 参数提出调用括号时,当方法无其他实参时省略调用括号。 ### Kotlin Lambda 语法 这里会发现 Kotlin 和其他语言在 lambda 的处理上不同。 其他语言,如 Java、JavaScript 一般是这样调用 ``` invoke(() => {}) ``` 部分语言会允许当只存在一个参数时,将 () 省略单独输入参数名。 同时部分语言允许当函数有返回值时,且为一行函数时忽略函数体的大括号。 比如: ``` invoke(a => a) ``` 但 Kotlin 的 lambda 参数不太一样。 Kotlin 的 lambda 参数声明是写在函数体大括号内的。 比如: Java 中 ``` Java invoke(a => {}) ``` Kotlin 中 ``` Kotlin invoke({a -> }) ``` Kotlin 这么设计的原因有两个。 在 Kotlin 中,有很多 lambda 的实际参数为 0 或 1。 而当没有参数时,Java 中的 lambda 是 `() => {}` 而 Kotlin 是 `{}` 当参数只有一个是,Java 中的 lambda 是 `it => {}` 而 Kotlin 是 `{it -> }` 而 Kotlin 进一步允许当参数只有一个时,参数名被默认命名为 it。 而 Kotlin 端 lambda 实际成了 `{}` 所以,如果 lambda 函数没有或只有一个 Kotlin 都可以做到不写参数名,更加简便。 ### Kotlin Lambda 特性 接下来到了 Kotlin 关于 lambda 的重要特性。 > Kotlin 允许位于函数末尾的 lambda 参数提出调用括号。 在 Java 中,我们创建一个线程,通常是。 ``` Java new Thread(() -> { // do something }) ``` 而在 Kotlin 中 Lambda 则是 ``` Kotlin Thread({ // do something }) ``` 而利用 该特性 得到的 Kotlin Lambda 则是 ``` Kotlin Thread() { // do something } ``` 同时可以配合这个特性: > Kotlin 允许在 lambda 参数提出调用括号时,当方法无其他实参时省略调用括号。 可以继续优化成 ``` Kotlin Thread { // do something } ``` ## 扩展函数与 lambda 配合 实际上 扩展函数 的用处不仅仅局限于做更方便的快捷调用函数。 而 lambda函数 本身也可以是 扩展函数。 就像 Kotlin 标准库中提供的 let/also/run/apply 都是通过这个特性实现的。 我们来看 run。 ``` Kotlin public inline fun <T, R> T.run(block: T.() -> R): R { return block() } ``` 函数 run 是一个扩展 T 的函数,他接受一个 lambda 函数作为参数,这个 lambda 函数是 T 的扩展函数。 这样,我们可以做到什么? ``` Kotlin "123".run { // 实际上这里的上下文 this 已经是 "123" 了。 } ``` 我们可以利用扩展函数实现对上下文 this 上 切换/附加 内容,这样可以用以配置不同的情况,甚至做到不同的东西。 我们也可以利用这个特性,将一些不太链式调用的链式起来。 例如: ``` Kotlin accessToken = Request.Builder() .url("https://api.weixin.qq.com/...") .build() .let { client.newCall(it) } .execute() .body ?.string() ?.toJsonNode() ?.get("access_token") ?.asText() ?: error("获取微信access_token失败") ``` # 总结 至此,我们理解了 Kotlin DSL 的根本特性。 Kotlin 之所以能写出“丝滑”的 DSL,本质上并不是因为语言专门内置了 DSL 功能,而是因为它具备两个核心特性: 1. **扩展函数** —— 可以把已有类的上下文(`this`)注入到新的作用域中,从而让 DSL 像是“内置”的语法。 2. **Lambda 的灵活语法** —— 特别是“尾随 lambda”与单参数默认名 `it`,让 DSL 代码看起来简洁、接近自然语言。 当这两点结合时,我们不仅能封装出链式调用、配置函数,还能像写 HTML、Gradle 脚本一样,写出语义清晰、层次分明的 DSL。 因此,Kotlin DSL 的根本,并不在于语法糖的表面,而在于 **扩展函数与 Lambda 的组合**,让语言本身成为创造 DSL 的土壤。 最后修改:2025 年 08 月 23 日 © 允许规范转载 赞 1 如果觉得我的文章对你有用,请随意赞赏