字符串操作:字符串模板、多行字符串、字符串常用函数

字符串操作,说白了就是 Kotlin 里最基础也最常用的活儿。我做了这么多年项目,几乎每个类都离不开字符串处理。今天咱们就把字符串模板、多行字符串,还有 split、replace、正则这几个高频函数彻底聊透。

1. 字符串模板:不只是拼接

Java 时代拼字符串有多痛苦?我记得刚转 Kotlin 那会儿,看到字符串模板简直想哭——终于不用写一堆加号和 String.format 了。

val name = "Kotlin"
val version = 1.9
// 简单模板
println("Hello, $name!")  // Hello, Kotlin!
// 表达式模板
println("${name.uppercase()} version ${version + 0.1}")  // KOTLIN version 2.0

我的习惯:能用模板就别用拼接。模板里写复杂表达式时,记得用花括号包起来,可读性会好很多。

你想想看,如果模板里要调用函数或者做运算,不加花括号会怎样?编译器会直接报错。嗯,这里要注意:$name.length 是合法的,但 $name.toUpperCase() 就不行,必须写成 ${name.uppercase()}

2. 多行字符串:告别转义噩梦

我在项目中遇到过最头疼的事,就是写 SQL 或者 JSON 时满屏的转义符。Kotlin 的三引号字符串完美解决了这个问题。

val sql = """
    SELECT id, name, age
    FROM users
    WHERE age > 18
    ORDER BY id DESC
""".trimIndent()

trimIndent() 会去掉公共前导空白,保持缩进整洁。如果你需要保留某些缩进,可以用 trimMargin() 自定义边界符。

我曾经踩过的坑:三引号字符串里不能直接写 $ 符号做模板插值?其实可以,但如果你要输出字面量的 $,就得用 ${'$'} 这种写法。说白了就是转义方式变了,但本质没变。

3. 字符串常用函数:split、replace、正则

这三个函数我几乎天天用。咱们一个一个说。

3.1 split:分割的艺术

Kotlin 的 split 比 Java 灵活得多。它可以接受多个分隔符,甚至正则表达式。

val str = "apple,banana;orange|grape"
// 多个分隔符
val fruits = str.split(",", ";", "|")
println(fruits)  // [apple, banana, orange, grape]

// 限制分割次数
val parts = str.split(",", limit = 2)
println(parts)  // [apple, "banana;orange|grape"]

我个人习惯用 splitToSequence 处理大字符串,它返回惰性序列,不会一次性创建所有子串,内存友好。

3.2 replace:替换的多种玩法

replace 函数有好几个重载版本。我项目中用得最多的是带 lambda 的版本,可以动态决定替换内容。

val text = "我的手机号是13812345678,他的手机号是13987654321"
// 用正则替换,隐藏中间四位
val masked = text.replace(Regex("(\\d{3})\\d{4}(\\d{4})")) {
    "${it.groupValues[1]}****${it.groupValues[3]}"
}
println(masked)  // 我的手机号是138****5678,他的手机号是139****4321

核心要点:replace 配合 lambda 可以做非常灵活的替换逻辑。你想想看,如果只是简单替换,用 replace(old, new) 就够了;但要做条件替换、动态替换,lambda 版本才是王道。

3.3 正则表达式:Regex 类

Kotlin 的 Regex 类比 Java 的 Pattern 好用太多。它直接提供了 matchescontainsMatchInfindAll 等方法,不用再手动创建 Matcher 对象。

val emailRegex = Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
val email = "test@example.com"

// 完全匹配
println(emailRegex.matches(email))  // true

// 查找所有匹配
val log = "Error at line 42: NullPointerException\nError at line 87: IndexOutOfBoundsException"
val errorRegex = Regex("Error at line (\\d+): (\\w+)")
val results = errorRegex.findAll(log)
results.forEach { match ->
    println("Line ${match.groupValues[1]}: ${match.groupValues[2]}")
}

我的建议:正则表达式尽量用原始字符串(三引号)写,少一层转义,可读性高很多。比如 Regex("""\d+""")Regex("\\d+") 清爽多了。

4. 知识体系总览

下面这张图把字符串操作的核心知识点串起来了。我画图时习惯把「模板」和「多行字符串」归为「创建与格式化」,把 split、replace、正则归为「处理与转换」。

Kotlin 字符串操作 创建与格式化 字符串模板:$name / ${expr} 多行字符串:"""...""" trimIndent() / trimMargin() 处理与转换 split:多分隔符 / 限制次数 replace:简单 / lambda 动态替换 Regex:matches / findAll / 分组 核心原则 模板优先于拼接 · 正则用原始字符串 · 大文本用惰性序列

5. 实战组合拳

最后分享一个我项目里真实用过的场景:解析日志文件,提取错误信息并脱敏。

val log = """
    [INFO] 2024-01-15 10:30:00 User login: user001
    [ERROR] 2024-01-15 10:30:05 NullPointerException at line 42
    [INFO] 2024-01-15 10:31:00 User logout: user001
    [ERROR] 2024-01-15 10:32:00 IndexOutOfBoundsException at line 87
""".trimIndent()

// 提取所有 ERROR 行
val errorLines = log.split("\n")
    .filter { it.startsWith("[ERROR]") }
    .map { line ->
        // 用正则提取异常类型和行号
        val regex = Regex("""\[ERROR\] .+? (\w+Exception) at line (\d+)""")
        val match = regex.find(line)
        if (match != null) {
            "异常: ${match.groupValues[1]}, 行号: ${match.groupValues[2]}"
        } else {
            "未知错误"
        }
    }

errorLines.forEach { println(it) }
// 输出:
// 异常: NullPointerException, 行号: 42
// 异常: IndexOutOfBoundsException, 行号: 87

你看,split 分割行,正则提取关键信息,filter 过滤,map 转换——这几个函数组合起来,处理文本数据非常顺手。

总结一下:字符串模板让拼接更优雅,多行字符串让格式化文本更干净,split、replace、正则这三个函数覆盖了 90% 的字符串处理需求。我建议你平时多练练这些组合用法,遇到实际需求时才能信手拈来。

公众号:蓝海资料掘金营,微信deep3321