1. 概述
驼峰命名(camel case)和下划线命名(snake case)是两种广泛使用的命名规范。
本文将介绍在 Kotlin 中如何实现这两种命名方式之间的相互转换。这类需求在实际开发中非常常见,比如处理数据库字段映射、API 接口参数转换时就容易踩坑——后端用 camelCase
,数据库或前端却习惯 snake_case
,不做好转换很容易出问题。
2. 驼峰命名与下划线命名简介
- 驼峰命名:单词首字母大写并拼接在一起,例如
FlywayMigrator
。Java 和 Kotlin 默认采用这种命名风格。 - 下划线命名:单词之间用下划线
_
分隔,例如flyway_migrator
。Python 等语言常用这种方式命名变量或函数。
我们接下来的目标就是在这两种格式之间安全、准确地转换。
3. 驼峰命名转下划线命名
目标是将类似 myNiceCamelCaseString
或 MyNiceCamelCaseString
转换为 my_nice_camel_case_string
。
先定义测试数据:
val camelCaseInput1 = "myNiceCamelCaseString"
val camelCaseInput2 = "MyNiceCamelCaseString"
val expectedInSnakeCase = "my_nice_camel_case_string"
✅ 注意:输入可能以大写或小写字母开头,转换逻辑需兼容这两种情况。
3.1 使用正则表达式配合 replace()
函数
核心思路是:把每个“前面有字符的大写字母”替换为“下划线 + 小写该字母”。
使用 Kotlin 的扩展函数实现如下:
fun String.camelToSnakeCase(): String {
val pattern = "(?<=.)[A-Z]".toRegex()
return this.replace(pattern, "_$0").lowercase()
}
🔍 关键点解析:
(<=?.)[A-Z]
是一个正向后行断言(positive look-behind),表示匹配任意一个前面存在字符的大写字母。- 这样做可以避免在字符串开头的大写字母前添加下划线(如
MyNice...
不会变成_my_nice...
)。 $0
表示整个匹配到的内容(即那个大写字母),替换后变成_X
。- 最后统一转为小写即可得到标准 snake_case。
✅ 单元测试验证:
assertEquals(expectedInSnakeCase, camelCaseInput1.camelToSnakeCase())
assertEquals(expectedInSnakeCase, camelCaseInput2.camelToSnakeCase())
通过测试,说明该方法能正确处理大小写开头的情况。
3.2 使用 fold()
函数(无正则方案)
如果你不想依赖正则表达式(比如出于性能或可读性考虑),可以用 fold()
逐字符处理:
fun String.camelToSnakeCaseNoRegex(): String {
return this.fold(StringBuilder()) { acc, c ->
acc.let {
val lowerC = c.lowercase()
acc.append(if (acc.isNotEmpty() && c.isUpperCase()) "_$lowerC" else lowerC)
}
}.toString()
}
✅ 核心逻辑:
- 使用
StringBuilder
提高性能(避免频繁创建字符串对象)。 - 只有当当前字符是大写 且 前面已有字符时,才插入下划线。
- 否则直接追加小写形式。
⚠️ 注意:isUpperCase()
判断的是单个字符是否为大写,适用于 ASCII 字符,对 Unicode 多字节字符需额外处理(本文暂不展开)。
测试验证:
assertEquals(expectedInSnakeCase, camelCaseInput1.camelToSnakeCaseNoRegex())
assertEquals(expectedInSnakeCase, camelCaseInput2.camelToSnakeCaseNoRegex())
两种方式结果一致,可根据团队风格选择使用。
4. 下划线命名转驼峰命名
现在反过来,将 one_good_snake_case_string
转为 oneGoodSnakeCaseString
。
准备测试数据:
val snakeCaseInput = "one_good_snake_case_string"
val expectedInCamelCase = "oneGoodSnakeCaseString"
目标是找到所有 _x
形式的子串,并将其替换为 X
(去掉下划线并将字母大写)。
4.1 正则替换基础版
fun String.snakeToCamelCase(): String {
val pattern = "_[a-z]".toRegex()
return replace(pattern) { it.value.last().uppercase() }
}
📌 解析:
- 匹配
_a
到_z
的模式。 it.value
是匹配的完整字符串(如_g
),取.last()
得到'g'
,再.uppercase()
变成'G'
。
4.2 使用分组优化(推荐)
更清晰的方式是使用捕获组:
fun String.snakeToCamelCase2(): String {
val pattern = "_([a-z])".toRegex()
return replace(pattern) { it.groupValues[1].uppercase() }
}
📌 说明:
([a-z])
将小写字母捕获为第一组。it.groupValues[1]
获取第一个捕获组内容(即字母本身)。- 更具可读性,也便于后续扩展(如支持数字等)。
✅ 测试验证:
assertEquals(expectedInCamelCase, snakeCaseInput.snakeToCamelCase())
assertEquals(expectedInCamelCase, snakeCaseInput.snakeToCamelCase2())
两者均能正确转换。
❌ 常见坑点提醒:
- 如果输入包含连续下划线(如
a__b
),上述方法会生成非法驼峰名(如aB
),但丢失中间信息。生产环境建议先规范化输入(如去除多余_
)。 - 若需支持首字母大写的 PascalCase(如
OneGoodSnakeCaseString
),可在最后手动首字母大写。
5. 总结
本文通过实例演示了在 Kotlin 中实现 camelCase ↔ snake_case 的互转方法:
方法 | 优点 | 缺点 |
---|---|---|
正则 + replace() |
简洁高效,适合大多数场景 ✅ | 对复杂边界情况需谨慎设计 pattern |
fold() 手动拼接 |
不依赖正则,逻辑透明 ❌ | 代码稍长,性能略低 |
📌 实际项目中,建议封装成工具类或扩展函数,提升复用性和一致性。
示例代码已托管至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-strings-3