Kotlin DSL 风格编程详解
一、什么是 DSL
DSL(Domain Specific Language) 即领域特定语言,是一种专门为某个特定领域或场景设计的编程语言或语法风格。
核心理念
DSL 的核心是用代码表达"做什么 "(What),而不是"怎么做"(How)。
生活中的类比
点餐场景
❌ 普通表达(命令式):
"请走到厨房,打开冰箱,拿出一个鸡蛋,打碎放入锅中,开火加热3分钟,翻面再加热2分钟,装盘,端出来。"
✅ DSL 表达(声明式):
"我要一个煎蛋。"
建房子场景
❌ 普通表达:
"先挖地基10米深,然后浇筑混凝土500立方米,然后砌砖1000块,然后安装铝合金窗户..."
✅ DSL 表达:
我要 {
三室一厅
两卫
南北通透
}
二、普通代码 vs DSL 风格对比
2.1 创建对象并设置属性
Java 普通代码
public class Person {
private String name;
private int age;
private String city;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
// 使用
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("张三");
person.setAge(20);
person.setCity("北京");
System.out.println(person.getName());
}
}
Kotlin 普通代码
data class Person(
var name: String = "",
var age: Int = 0,
var city: String = ""
)
// 使用
fun main() {
val person = Person()
person.name = "张三"
person.age = 20
person.city = "北京"
println(person.name)
}
Kotlin DSL 风格
data class Person(
var name: String = "",
var age: Int = 0,
var city: String = ""
)
// DSL 构建函数
fun person(block: Person.() -> Unit): Person {
return Person().apply(block)
}
// 使用
fun main() {
val person = person {
name = "张三"
age = 20
city = "北京"
}
println(person.name)
}
2.2 条件判断
Java 普通代码
public class Release {
private String releaseStatus;
private String releaseRemark;
public String getReleaseStatus() {
return releaseStatus;
}
public void setReleaseStatus(String releaseStatus) {
this.releaseStatus = releaseStatus;
}
public String getReleaseRemark() {
return releaseRemark;
}
public void setReleaseRemark(String releaseRemark) {
this.releaseRemark = releaseRemark;
}
}
public class ConditionUtils {
public static boolean checkCondition(Release release) {
return "1".equals(release.getReleaseStatus()) && release.getReleaseRemark() != null;
}
}
// 使用
public class Main {
public static void main(String[] args) {
Release release = new Release();
release.setReleaseStatus("1");
release.setReleaseRemark("备注内容");
boolean result = ConditionUtils.checkCondition(release);
System.out.println(result);
}
}
Kotlin 普通代码
data class Release(
var releaseStatus: String? = null,
var releaseRemark: String? = null
)
// 使用
fun main() {
val release = Release(
releaseStatus = "1",
releaseRemark = "备注内容"
)
val result = release.releaseStatus == "1" && release.releaseRemark != null
println(result)
}
Kotlin DSL 风格
data class Release(
var releaseStatus: String? = null,
var releaseRemark: String? = null
)
// DSL 条件构建器
class ConditionBuilder<T> {
private val conditions = mutableListOf<(T) -> Boolean>()
fun field(property: kotlin.reflect.KProperty1<T, *>, value: Any?) {
conditions.add { data -> property.get(data) == value }
}
fun fieldIsNotNull(property: kotlin.reflect.KProperty1<T, *>) {
conditions.add { data -> property.get(data) != null }
}
fun build(): (T) -> Boolean = { data ->
conditions.all { it(data) }
}
}
// DSL 函数
fun <T> condition(block: ConditionBuilder<T>.() -> Unit): (T) -> Boolean {
val builder = ConditionBuilder<T>()
builder.block()
return builder.build()
}
// 使用
fun main() {
val release = Release(
releaseStatus = "1",
releaseRemark = "备注内容"
)
val checkCondition = condition<Release> {
field(Release::releaseStatus, "1")
fieldIsNotNull(Release::releaseRemark)
}
val result = checkCondition(release)
println(result) // true
}
2.3 构建列表
Java 普通代码
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("苹果");
list.add("香蕉");
list.add("橙子");
for (String item : list) {
System.out.println(item);
}
}
}
Kotlin 普通代码
fun main() {
val list = mutableListOf<String>()
list.add("苹果")
list.add("香蕉")
list.add("橙子")
for (item in list) {
println(item)
}
}
Kotlin DSL 风格
// DSL 列表构建函数
fun <T> list(block: MutableList<T>.() -> Unit): List<T> {
return mutableListOf<T>().apply(block)
}
// 使用
fun main() {
val fruits = list<String> {
add("苹果")
add("香蕉")
add("橙子")
}
fruits.forEach { println(it) }
}
2.4 构建嵌套对象
Java 普通代码
import java.util.ArrayList;
import java.util.List;
public class Address {
private String province;
private String city;
private String street;
public String getProvince() { return province; }
public void setProvince(String province) { this.province = province; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
}
public class User {
private String name;
private int age;
private Address address;
private List<String> hobbies;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
public List<String> getHobbies() { return hobbies; }
public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; }
}
// 使用
public class Main {
public static void main(String[] args) {
User user = new User();
user.setName("张三");
user.setAge(25);
Address address = new Address();
address.setProvince("广东省");
address.setCity("深圳市");
address.setStreet("科技路");
user.setAddress(address);
List<String> hobbies = new ArrayList<>();
hobbies.add("游泳");
hobbies.add("阅读");
user.setHobbies(hobbies);
System.out.println(user.getName());
}
}
Kotlin DSL 风格
data class Address(
var province: String = "",
var city: String = "",
var street: String = ""
)
data class User(
var name: String = "",
var age: Int = 0,
var address: Address = Address(),
var hobbies: MutableList<String> = mutableListOf()
)
// DSL 构建函数
fun user(block: User.() -> Unit): User = User().apply(block)
fun User.address(block: Address.() -> Unit) {
address = Address().apply(block)
}
// 使用
fun main() {
val user = user {
name = "张三"
age = 25
address {
province = "广东省"
city = "深圳市"
street = "科技路"
}
hobbies = mutableListOf("游泳", "阅读")
}
println(user.name)
println(user.address.city)
}
三、Kotlin 实现 DSL 的核心语法特性
Kotlin DSL 之所以简洁优雅,主要依赖以下几个核心语法特性:
3.1 带接收者的 Lambda(Lambda with Receiver)
这是 Kotlin DSL 最重要的特性。
语法说明
// 普通 Lambda
block: (T) -> Unit
// 调用方式:block(receiver)
// Lambda 内部:需要用参数名访问 receiver
// 带接收者的 Lambda
block: T.() -> Unit
// 调用方式:receiver.block()
// Lambda 内部:this 指向 receiver,可直接访问其成员
Java 对比
// Java 只有普通 Lambda(Java 8+)
// 没有带接收者的 Lambda
// Java 实现
public class Person {
private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
public class PersonBuilder {
public static void withPerson(Person person, Consumer<Person> block) {
block.accept(person);
}
}
// 使用
public class Main {
public static void main(String[] args) {
Person person = new Person();
PersonBuilder.withPerson(person, p -> {
p.setName("张三"); // 必须用参数名 p
p.setAge(20);
});
}
}
Kotlin 实现
data class Person(
var name: String = "",
var age: Int = 0
)
fun person(block: Person.() -> Unit): Person {
return Person().apply(block)
}
// 使用
fun main() {
val person = person {
name = "张三" // 直接访问,不需要参数名
age = 20
}
}
对比总结
| 特性 |
Java |
Kotlin |
| Lambda 类型 |
(T) -> Unit |
T.() -> Unit |
| 调用方式 |
block.accept(receiver) |
receiver.block() |
| 内部访问 |
p.setName("张三") |
name = "张三" |
| 语法糖 |
❌ 无 |
✅ this 可省略 |
3.2 扩展函数(Extension Function)
Java 对比
// Java:必须写工具类
public class StringUtils {
public static boolean isNotEmpty(String str) {
return str != null && !str.isEmpty();
}
}
// 使用
public class Main {
public static void main(String[] args) {
String name = "张三";
boolean result = StringUtils.isNotEmpty(name);
}
}
Kotlin 实现
// Kotlin:扩展函数
fun String.isNotEmpty(): Boolean {
return this != null && this.isNotEmpty()
}
// 使用
fun main() {
val name = "张三"
val result = name.isNotEmpty() // 像调用成员方法一样
}
在 DSL 中的应用
// 为 String 添加 DSL 风格的扩展函数
fun String.ifNotEmpty(block: (String) -> Unit) {
if (this.isNotEmpty()) {
block(this)
}
}
// 使用
fun main() {
val name = "张三"
name.ifNotEmpty {
println("名字是: $it")
}
}
对比总结
| 特性 |
Java |
Kotlin |
| 实现方式 |
工具类静态方法 |
扩展函数 |
| 调用方式 |
Utils.method(obj) |
obj.method() |
| 语法体验 |
函数调用 |
像成员方法 |
3.3 Lambda 在括号外
Java 对比
// Java:Lambda 必须在括号内
public class Main {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c");
// Lambda 必须在括号内
list.forEach(item -> System.out.println(item));
}
}
Kotlin 实现
fun main() {
val list = listOf("a", "b", "c")
// 方式1:Lambda 在括号内
list.forEach({ item -> println(item) })
// 方式2:Lambda 在括号外(语法糖)
list.forEach { item ->
println(item)
}
// 方式3:如果只有一个参数,可用 it
list.forEach { println(it) }
}
在 DSL 中的应用
data class Person(var name: String = "", var age: Int = 0)
// 如果最后一个参数是 Lambda,可以放在括号外
fun person(block: Person.() -> Unit): Person = Person().apply(block)
fun main() {
// 这些写法都等价
val p1 = person({ name = "张三" }) // Lambda 在括号内
val p2 = person() { name = "张三" } // Lambda 在括号外
val p3 = person { name = "张三" } // 括号可省略
}
对比总结
| 特性 |
Java |
Kotlin |
| Lambda 位置 |
必须在括号内 |
可在括号外 |
| 空括号处理 |
必须写 () |
可省略 () |
| DSL 友好度 |
❌ 一般 |
✅ 非常友好 |
3.4 中缀函数(Infix Function)
Java 对比
// Java:只能用普通方法调用
public class Pair {
private String first;
private String second;
public Pair(String first, String second) {
this.first = first;
this.second = second;
}
public String getFirst() { return first; }
public String getSecond() { return second; }
}
// 使用
public class Main {
public static void main(String[] args) {
Pair pair = new Pair("key", "value");
System.out.println(pair.getFirst());
}
}
Kotlin 实现
// 中缀函数
infix fun String.to(that: String): Pair<String, String> {
return Pair(this, that)
}
// 使用
fun main() {
// 普通调用
val pair1 = "key".to("value")
// 中缀调用(更接近自然语言)
val pair2 = "key" to "value"
println(pair2.first) // key
}
在 DSL 中的应用
// Map 构建
fun main() {
// 普通写法
val map1 = mapOf(Pair("name", "张三"), Pair("age", "20"))
// 中缀函数 DSL 风格
val map2 = mapOf(
"name" to "张三",
"age" to "20"
)
println(map2["name"])
}
对比总结
| 特性 |
Java |
Kotlin |
| 调用语法 |
obj.method(arg) |
obj method arg |
| 自然语言感 |
❌ 无 |
✅ 像 "key to value" |
| DSL 友好度 |
❌ 一般 |
✅ 非常友好 |
3.5 操作符重载(Operator Overloading)
Java 对比
// Java:不支持操作符重载
public class Money {
private int amount;
public Money(int amount) {
this.amount = amount;
}
public int getAmount() { return amount; }
// 只能用普通方法
public Money add(Money other) {
return new Money(this.amount + other.amount);
}
}
// 使用
public class Main {
public static void main(String[] args) {
Money m1 = new Money(100);
Money m2 = new Money(50);
Money total = m1.add(m2); // 只能调用方法
}
}
Kotlin 实现
data class Money(val amount: Int) {
// 操作符重载
operator fun plus(other: Money): Money {
return Money(this.amount + other.amount)
}
operator fun minus(other: Money): Money {
return Money(this.amount - other.amount)
}
}
// 使用
fun main() {
val m1 = Money(100)
val m2 = Money(50)
val total = m1 + m2 // 使用 + 操作符
val diff = m1 - m2 // 使用 - 操作符
println(total.amount) // 150
}
在 DSL 中的应用
data class Path(val segments: MutableList<String> = mutableListOf()) {
operator fun String.unaryPlus() {
segments.add(this)
}
operator fun plus(other: Path): Path {
return Path((segments + other.segments).toMutableList())
}
}
fun path(block: Path.() -> Unit): Path = Path().apply(block)
// 使用
fun main() {
val p = path {
+"home"
+"user"
+"documents"
}
println(p.segments) // [home, user, documents]
}
对比总结
| 特性 |
Java |
Kotlin |
| 操作符重载 |
❌ 不支持 |
✅ 支持 |
| 调用方式 |
m1.add(m2) |
m1 + m2 |
| DSL 友好度 |
❌ 差 |
✅ 好 |
四、Java vs Kotlin 实现 DSL 完整对比
4.1 场景:构建 HTML
Java 实现
import java.util.ArrayList;
import java.util.List;
// HTML 标签基类
public abstract class HtmlElement {
protected String tagName;
protected List<HtmlElement> children = new ArrayList<>();
protected String textContent;
public void addChild(HtmlElement element) {
children.add(element);
}
public void setText(String text) {
this.textContent = text;
}
public abstract String render();
}
// Div 标签
public class Div extends HtmlElement {
public Div() {
this.tagName = "div";
}
@Override
public String render() {
StringBuilder sb = new StringBuilder();
sb.append("<").append(tagName).append(">");
if (textContent != null) {
sb.append(textContent);
}
for (HtmlElement child : children) {
sb.append(child.render());
}
sb.append("</").append(tagName).append(">");
return sb.toString();
}
}
// P 标签
public class P extends HtmlElement {
public P() {
this.tagName = "p";
}
@Override
public String render() {
return "<p>" + (textContent != null ? textContent : "") + "</p>";
}
}
// HTML 构建器
public class HtmlBuilder {
private Div root;
public HtmlBuilder() {
this.root = new Div();
}
public HtmlBuilder div(Consumer<Div> block) {
Div div = new Div();
block.accept(div);
root.addChild(div);
return this;
}
public HtmlBuilder p(String text) {
P p = new P();
p.setText(text);
root.addChild(p);
return this;
}
public String build() {
return root.render();
}
}
// 使用
public class Main {
public static void main(String[] args) {
HtmlBuilder builder = new HtmlBuilder();
String html = builder
.div(div -> {
div.addChild(new P());
// 很难继续嵌套...
})
.p("Hello World")
.build();
System.out.println(html);
}
}
Kotlin DSL 实现
// HTML 标签基类
open class Tag(val name: String) {
val children = mutableListOf<Tag>()
val attributes = mutableMapOf<String, String>()
fun <T : Tag> initTag(tag: T, init: T.() -> Unit): T {
tag.init()
children.add(tag)
return tag
}
override fun toString(): String {
val attrs = if (attributes.isEmpty()) "" else attributes.entries.joinToString(" ") { "${it.key}=\"${it.value}\"" }
val content = children.joinToString("")
return "<$name $attrs>$content</$name>"
}
}
// 具体标签
class HTML : Tag("html") {
fun head(init: HEAD.() -> Unit) = initTag(HEAD(), init)
fun body(init: BODY.() -> Unit) = initTag(BODY(), init)
}
class HEAD : Tag("head") {
fun title(init: TITLE.() -> Unit) = initTag(TITLE(), init)
}
class TITLE : Tag("title")
class BODY : Tag("body") {
fun div(init: DIV.() -> Unit) = initTag(DIV(), init)
fun p(init: P.() -> Unit) = initTag(P(), init)
}
class DIV : Tag("div") {
fun p(init: P.() -> Unit) = initTag(P(), init)
fun span(init: SPAN.() -> Unit) = initTag(SPAN(), init)
}
class P : Tag("p") {
operator fun String.unaryPlus() {
children.add(Text(this))
}
}
class SPAN : Tag("span")
class Text(private val text: String) : Tag("") {
override fun toString() = text
}
// DSL 入口函数
fun html(init: HTML.() -> Unit): HTML {
return HTML().apply(init)
}
// 使用
fun main() {
val result = html {
head {
title {
// 标题内容
}
}
body {
div {
p {
+"Hello World"
}
p {
+"This is a paragraph"
}
}
div {
p {
+"Another paragraph"
}
}
}
}
println(result)
}
4.2 场景:构建 SQL 查询
Java 实现
import java.util.ArrayList;
import java.util.List;
public class SqlBuilder {
private String tableName;
private List<String> columns = new ArrayList<>();
private List<String> whereConditions = new ArrayList<>();
private String orderBy;
private Integer limit;
public SqlBuilder select(String... cols) {
for (String col : cols) {
columns.add(col);
}
return this;
}
public SqlBuilder selectAll() {
columns.add("*");
return this;
}
public SqlBuilder from(String table) {
this.tableName = table;
return this;
}
public SqlBuilder where(String condition) {
whereConditions.add(condition);
return this;
}
public SqlBuilder orderBy(String column) {
this.orderBy = column;
return this;
}
public SqlBuilder limit(int n) {
this.limit = n;
return this;
}
public String build() {
StringBuilder sb = new StringBuilder();
sb.append("SELECT ");
if (columns.isEmpty()) {
sb.append("*");
} else {
sb.append(String.join(", ", columns));
}
sb.append(" FROM ").append(tableName);
if (!whereConditions.isEmpty()) {
sb.append(" WHERE ");
sb.append(String.join(" AND ", whereConditions));
}
if (orderBy != null) {
sb.append(" ORDER BY ").append(orderBy);
}
if (limit != null) {
sb.append(" LIMIT ").append(limit);
}
return sb.toString();
}
}
// 使用
public class Main {
public static void main(String[] args) {
String sql = new SqlBuilder()
.select("id", "name", "age")
.from("users")
.where("age > 18")
.where("status = 'active'")
.orderBy("name")
.limit(10)
.build();
System.out.println(sql);
// SELECT id, name, age FROM users WHERE age > 18 AND status = 'active' ORDER BY name LIMIT 10
}
}
Kotlin DSL 实现
class SqlBuilder {
private var tableName = ""
private val columns = mutableListOf<String>()
private val whereConditions = mutableListOf<String>()
private var orderByClause: String? = null
private var limitValue: Int? = null
fun select(vararg cols: String) {
columns.addAll(cols)
}
fun selectAll() {
columns.add("*")
}
fun from(table: String) {
tableName = table
}
fun where(condition: String) {
whereConditions.add(condition)
}
fun orderBy(column: String) {
orderByClause = column
}
fun limit(n: Int) {
limitValue = n
}
fun build(): String {
val cols = if (columns.isEmpty()) "*" else columns.joinToString(", ")
val where = if (whereConditions.isNotEmpty()) {
" WHERE ${whereConditions.joinToString(" AND ")}"
} else ""
val order = orderByClause?.let { " ORDER BY $it" } ?: ""
val lim = limitValue?.let { " LIMIT $it" } ?: ""
return "SELECT $cols FROM $tableName$where$order$lim"
}
}
// DSL 函数
fun sql(block: SqlBuilder.() -> String): String {
val builder = SqlBuilder()
builder.block()
return builder.build()
}
// DSL 扩展函数
fun SqlBuilder.select(block: SelectBuilder.() -> Unit) {
val selectBuilder = SelectBuilder()
selectBuilder.block()
columns.addAll(selectBuilder.columns)
}
class SelectBuilder {
val columns = mutableListOf<String>()
operator fun String.unaryPlus() {
columns.add(this)
}
}
// 使用
fun main() {
// 简单 DSL
val sql1 = sql {
select("id", "name", "age")
from("users")
where("age > 18")
where("status = 'active'")
orderBy("name")
limit(10)
build()
}
println(sql1)
// 更优雅的 DSL(使用 + 操作符)
val sql2 = sql {
select {
+"id"
+"name"
+"age"
}
from("users")
where("age > 18")
where("status = 'active'")
orderBy("name")
limit(10)
build()
}
println(sql2)
}
五、总结
语法特性对比表
| 特性 |
Java |
Kotlin |
DSL 友好度 |
| 带接收者的 Lambda |
❌ 不支持 |
✅ 支持 |
⭐⭐⭐⭐⭐ |
| 扩展函数 |
❌ 不支持 |
✅ 支持 |
⭐⭐⭐⭐ |
| Lambda 在括号外 |
❌ 不支持 |
✅ 支持 |
⭐⭐⭐⭐⭐ |
| 中缀函数 |
❌ 不支持 |
✅ 支持 |
⭐⭐⭐⭐ |
| 操作符重载 |
❌ 不支持 |
✅ 支持 |
⭐⭐⭐⭐ |
| 属性访问简化 |
❌ getter/setter |
✅ 直接访问 |
⭐⭐⭐⭐ |
| 默认参数 |
❌ 不支持 |
✅ 支持 |
⭐⭐⭐ |
| 命名参数 |
❌ 不支持 |
✅ 支持 |
⭐⭐⭐ |
DSL 适用场景
| 场景 |
说明 |
| 配置构建 |
Gradle、Kotlin DSL 配置 |
| UI 布局 |
Compose、Anko |
| 数据查询 |
SQL DSL、MongoDB DSL |
| 测试框架 |
Kotest、MockK |
| HTML/XML |
Kotlinx.html |
| 表单验证 |
本项目的 condition DSL |
核心要点
- DSL 本质:用代码表达"做什么",而不是"怎么做"
- Kotlin 优势:多个语法特性共同作用,让 DSL 简洁优雅
- Java 现状:可以用 Builder 模式模拟,但代码量更大
- 最佳实践:DSL 要语义清晰、易于理解,不要过度设计