Kotlin 字符串

字符串是日常编程用得最多的类型之一——日志、UI 文案、配置文件、网络报文全是字符串。Kotlin 的字符串和 Java 一脉相承(同样不可变,同样基于 UTF-16),但通过字符串模板丰富的扩展函数大幅提升了表达力。本篇逐个讲。

1. 字符串基础

Kotlin 字符串是 String 类型,用双引号包裹。和 Java/Python 一样,字符串不可变——任何修改都返回新字符串:

// 字符串声明:String 类型,双引号
val s1: String = "hello"
val s2 = "你好,Kotlin"

// 字符串【不可变】:改字符串其实是创建新对象
val original = "abc"
val modified = original.uppercase()
println(original)           // abc(原字符串没变)
println(modified)           // ABC

// 单个字符:Char,单引号
val c: Char = 'A'
val first = s1[0]           // 'h',可以用索引取字符
val len = s1.length         // 5

单个字符用 Char(单引号),区别于字符串(双引号)。可以用下标访问字符 s[0],但不能赋值(s[0] = 'x' 是错的,因为不可变)。

2. 字符串模板(招牌特性)

这是 Kotlin 比 Java 简洁的核心特性之一——直接在字符串里嵌入变量和表达式:

// 字符串模板:Kotlin 甜点
val name = "小明"
val age = 25

// 简单变量:$ 后跟变量名
val g1 = "你好, $name"
println(g1)                  // 你好, 小明

// 表达式:用 ${} 包裹
val g2 = "明年 ${age + 1} 岁"
println(g2)                  // 明年 26 岁

// 调用方法
val g3 = "长度 ${name.length}"
println(g3)                  // 长度 2

// 嵌套引号:${} 里用双引号
val s = "结果是 ${if (age > 18) "成年" else "未成年"}"
println(s)                   // 结果是 成年

// 字符串模板大幅减少字符串拼接
// 对比 Java:
// String s = "你好, " + name + "!明年 " + (age+1) + " 岁。"

对比 Java 的 "你好, " + name + "!明年 " + (age+1) + " 岁。",Kotlin 的模板直观得多。再也不需要 StringBuilderString.format 这种啰嗦写法。

3. 原始字符串(Raw String,多行)

三个双引号 """ 包裹的字符串叫原始字符串,可以跨行、保留缩进、不识别转义符:

// 原始字符串(Raw String):三个引号,保留格式,不转义
val text = """
    第一行
    第二行
        缩进
    第三行
"""
println(text)

// 问题:多行字符串常有"缩进污染"(因为代码缩进)
// 用 trimMargin() 去除
val clean = """
    |第一行
    |第二行
    |第三行
""".trimMargin()
// 默认以 | 作为 margin 前缀,删掉每行 | 及之前的空白

// 自定义前缀
val clean2 = """
    >第一行
    >第二行
""".trimMargin(">")

// 原始字符串【不识别转义符】
val noEscape = """\n 不是换行,是字面的反斜杠 + n"""
println(noEscape)
// 输出: \n 不是换行,是字面的反斜杠 + n

原始字符串的常见用途:写 JSON 模板、SQL 查询、HTML 代码、Markdown 文档。配合 trimMargin() 去除代码缩进污染,效果非常干净。

注意原始字符串不识别转义符,所以 \\n 在里面就是字面的反斜杠加 n,要换行就直接换行。但字符串模板仍然有效(\\$var 会被替换)。

4. 基本操作

Kotlin 把所有常用操作做成了 String 的扩展函数,API 设计统一、好记:

// 长度、判空
val s = "Hello"
println(s.length)                  // 5
println(s.isEmpty())               // false
println(s.isNotEmpty())            // true
val blank = "   "
println(blank.isBlank())           // true(只含空白)
println("".isBlank())              // true(空字符串)

// 大小写
println("hello".uppercase())       // HELLO
println("WORLD".lowercase())       // world

// 去除空白
println("  hi  ".trim())           // hi
println("  hi  ".trimStart())      // hi(右侧保留)
println("xxhiyy".trim('x', 'y'))   // hi(去指定字符)

// 子串
val sub = "Hello".substring(1, 4)  // ell(闭开区间)
val sub2 = "Hello".take(3)         // Hel(前 3 个)
val sub3 = "Hello".takeLast(2)     // lo(后 2 个)

注意 isEmpty() vs isBlank():前者只检查长度为 0,后者还把"全是空白字符"也算 true。同理 isNotEmpty() vs isNotBlank()

5. 查找与替换

// 查找
val s = "Hello, World"
println(s.startsWith("Hello"))     // true
println(s.endsWith("World"))       // true
println(s.contains("World"))       // true
println(s.indexOf("o"))            // 4(第一个 o 的位置)
println(s.lastIndexOf("o"))        // 8(最后一个 o 的位置)

// 替换
val r1 = s.replace("World", "Kotlin")
println(r1)                        // Hello, Kotlin

// 用正则替换
val r2 = "abc123".replace(Regex("\\d+"), "#")
println(r2)                        // abc#

// 分割
val parts = "a,b,c".split(",")
println(parts)                     // [a, b, c]

val csv = "1,2,,3".split(",")
println(csv)                       // [1, 2, , 3](空字段保留)

// 多分隔符
val mixed = "a,b;c".split(",", ";")
println(mixed)                     // [a, b, c]

注意 split 接受可变参数,可以一次指定多个分隔符。要按正则分割用 split(Regex)

6. 字符串比较

Kotlin 的字符串比较语义清晰:

// 字符串比较
val a = "hello"
val b = "hello"
val c = "Hello"

// == :【值相等】,调用 equals()
println(a == b)                    // true
println(a == c)                    // false(大小写敏感)

// equals 带参数:忽略大小写
println(a.equals(c, ignoreCase = true))  // true

// === :引用相等(通常没必要用)
println(a === b)                   // 取决于字符串池,通常 true

// compareTo:字典序比较
println("abc".compareTo("abd"))    // -1(abc < abd)
println("abc".compareTo("abc"))    // 0(相等)
println("abd".compareTo("abc"))    // 1(abd > abc)

// 自然顺序排序
val sorted = listOf("banana", "apple", "cherry").sorted()
println(sorted)                    // [apple, banana, cherry]

关键点:== 是值相等(等价 equals),不是 Java 的引用相等。这避免了 Java 初学者最常见的坑:"a" == new String("a") 在 Java 里是 false,但 Kotlin 永远是 true。

7. 字符串构建

当需要拼接大量字符串时,用 buildStringStringBuilder(底层就是 Java 的 StringBuilder):

// 重复字符串
val hr = "-".repeat(20)
println(hr)                        // --------------------

// 字符串数组用 joinToString 拼接
val list = listOf("苹果", "香蕉", "橙子")
val joined = list.joinToString(", ")
println(joined)                    // 苹果, 香蕉, 橙子

// 自定义前后缀和分隔符
val formatted = list.joinToString(
    separator = " | ",
    prefix = "[",
    postfix = "]"
)
println(formatted)                 // [苹果 | 香蕉 | 橙子]

// 用 joinTo 拼到 StringBuilder
val sb = StringBuilder()
sb.append("水果: ")
list.joinTo(sb, ", ")
println(sb)                        // 水果: 苹果, 香蕉, 橙子

// 字符串构建:StringBuilder(用于大量拼接)
val build = buildString {
    for (i in 1..5) {
        append("行 $i\n")
    }
}
println(build)

buildString { ... } 是个内联高阶函数,块内 this 是 StringBuilder,适合构建复杂字符串(如 JSON、XML)。比"+"拼接快得多(因为 StringBuilder 是可变的)。

8. 数值与字符串互转

// 字符串转数值
val n1 = "42".toInt()              // 42(Int)
val n2 = "3.14".toDouble()         // 3.14(Double)
val n3 = "0xFF".toInt(16)          // 255(16 进制)

// 安全转换:失败返回 null
val safe: Int? = "abc".toIntOrNull()      // null
val safe2: Int? = "42".toIntOrNull()      // 42

// 浮点
val d: Double? = "abc".toDoubleOrNull()   // null

// 数值转字符串
val s1 = 42.toString()             // "42"
val s2 = 3.14.toString()           // "3.14"

// 格式化(类似 Java String.format)
val pi = 3.14159265
println("%.2f".format(pi))         // 3.14(保留 2 位)
println("%010.2f".format(pi))      // 0000003.14(0 填充,总宽 10)

// Kotlin 也支持 String.format
val s3 = String.format("%.4f", pi) // 3.1416

关键:不要直接 "abc".toInt()(会抛异常),用 toIntOrNull() 安全转换,配合 Elvis 给默认值:val n = s.toIntOrNull() ?: 0

9. 实战:模板字符串生成报表

data class Score(val name: String, val score: Int)

fun buildReport(scores: List<Score>): String {
    val average = if (scores.isEmpty()) 0.0 else scores.map { it.score }.average()
    val best = scores.maxByOrNull { it.score }
    val worst = scores.minByOrNull { it.score }

    return buildString {
        appendLine("=== 成绩报表 ===")
        appendLine()

        // 用 forEach 遍历 + 字符串模板
        scores.sortedByDescending { it.score }.forEachIndexed { i, s ->
            appendLine("${i + 1}. ${s.name} - ${s.score} 分")
        }

        appendLine()
        appendLine("平均分: ${"%.1f".format(average)}")
        appendLine("最高: ${best?.name} ${best?.score} 分")
        appendLine("最低: ${worst?.name} ${worst?.score} 分")
    }
}

val scores = listOf(
    Score("小明", 92),
    Score("小红", 88),
    Score("小刚", 75)
)
println(buildReport(scores))

这个例子综合用了 buildString、字符串模板、可空调用、Elvis、List 扩展函数。一个真实的项目里,字符串处理几乎都是这种模式。

10. 常用 API 速查表

小结

这一章你掌握了 Kotlin 字符串的所有姿势:模板插值、原始字符串、常用操作、查找替换、比较、构建、数值互转。最大的甜点是字符串模板(告别 Java 的字符串拼接)和原始字符串(优雅写多行文本)。下一篇看 集合——List、Set、Map,Kotlin 集合的 API 极其丰富。

← 上一篇 Kotlin 空安全

下一篇 Kotlin 集合

✈️💬