JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · LANGUAGE · JEP 378

文本块:多行字符串的可读写法

JDK 17 · 速查主题 05 / 06 · 预计 15 分钟
基础15 分钟JEP 378
一句话文本块把 SQL、JSON、HTML 从字符串拼接里解放出来,但它的规则不是「原样保留」:编译器会去掉附带空白,行尾空格和缩进都会影响结果。

学完你要能自己回答

  1. 附带空白(incidental whitespace)是怎么计算的?结束分隔符为什么影响结果?
  2. \s 与行尾 \ 分别解决什么问题?
  3. 文本块能做什么、不能做什么?为什么它不能替代模板引擎?

语法与附带空白01

文本块用三个双引号开始,开分隔符后必须换行。编译器会计算所有非空行与结束分隔符行的最小缩进,把这个「附带空白」从每一行去掉,保留相对缩进。

String json = """
        {
          "name": "Ada",
          "active": true
        }
        """;
System.out.print(json);
{
  "name": "Ada",
  "active": true
}
  • 开分隔符后必须换行;内容不能与 """ 写在同一行。
  • 结束分隔符单独占一行时,结果末尾带一个换行;放在内容行尾则不带。
  • 结束分隔符的缩进参与最小缩进计算;它比内容更靠左时,内容会保留多余空格。
  • 文本块仍然是 String,类型、不可变性、方法都与普通字符串一致。
不要把 IDE 的自动缩进当成结果。复制文本块时,结束分隔符的位置变了,字符串内容就可能变。

缩进由结束分隔符决定02

同样两行内容,结束分隔符放在 8 个空格处还是 4 个空格处,结果不同。最小缩进取「非空内容行」与「结束分隔符行」的最小值,再从每行去掉这么多空格。

String a = """
        one
        two
        """;
String b = """
        one
        two
    """;
System.out.println("a=" + a.lines().map(l -> "[" + l + "]").toList());
System.out.println("b=" + b.lines().map(l -> "[" + l + "]").toList());
a=[[one], [two]]
b=[[    one], [    two]]

a 的结束分隔符与内容同为 8 个空格,最小缩进是 8,内容被完全去掉缩进;b 的结束分隔符只有 4 个空格,最小缩进变成 4,内容保留了 4 个空格。这就是「结束分隔符影响结果」的具体含义。

写法结果原因
结束分隔符与内容对齐去掉公共缩进,保留相对缩进最小缩进等于内容缩进
结束分隔符更靠左内容保留多余空格最小缩进被结束分隔符拉低
行尾有空格默认被去掉行尾空白属于附带空白
行尾写 \s保留一个空格\s 是显式空格转义
需要保留行尾空格时,用 \s 而不是直接敲空格;直接敲的空格会在编译期被剥离。

转义:\s、行尾 \ 与引号03

文本块仍然处理 Java 转义序列,只是不需要再为每个换行写 \n。\s 表示一个空格,行尾的 \ 表示「不换行」,把下一行接上来。

String joined = """
        alpha \
        beta
        """;
System.out.println("[" + joined.replace("\n", "\\n") + "]");

String padded = """
        x\s
        y
        """;
padded.lines().forEach(line -> System.out.println("[" + line + "]"));
[alpha beta\n]
[x ]
[y]
  • 行尾 \ 去掉换行,但不会自动加空格;需要空格时在 \ 前写一个空格或用 \s。
  • \s 是一个空格,专门用于对抗行尾空白剥离。
  • 内容里要出现 """ 时,至少转义一个引号:\"""。
  • 文本块会处理 \n、\t、\\ 等转义;写 JSON 时,JSON 自身的反斜杠要再转义一层。
文本块不是 raw string。它减少的是换行和引号的噪音,不改变 Java 的转义规则;正则、Windows 路径、JSON 转义仍然要写双反斜杠。

格式化与边界04

文本块本身没有插值。需要占位替换时,用 String.formatted(JDK 15 起)或 String.format;需要模板引擎时,文本块只是承载模板文本的容器。

String sql = """
        SELECT *
        FROM %s
        WHERE id = %d
        """.formatted("users", 42);
System.out.print(sql);
SELECT *
FROM users
WHERE id = 42
  • formatted 使用 Formatter 语法,%s、%d、%02d 等与 String.format 一致。
  • 文本块可以用 + 拼接,但拼接后不再是编译期常量。
  • 文本块适合 SQL、JSON、HTML、测试期望值;不适合超长日志模板或需要复杂逻辑的渲染。
  • 17 没有字符串模板(String Templates 在 21 预览、23 撤回),不要期待 ${} 插值。
把用户输入直接拼进 SQL/HTML 文本块,不会自动获得安全性。文本块只改善可读性,不改变注入风险;该用参数化查询和转义的地方仍要用。

常见坑05

  • 结束分隔符的位置改变缩进;代码格式化工具重排时,文本块内容可能跟着变。
  • 行尾空格默认被去掉,测试断言里带尾随空格的字符串会失败。
  • Windows 的 CRLF 在文本块里按行终止符处理,但运行时字符串的换行仍是 \n。
  • JSON 里的 \n 要写成 \\n,否则文本块会先把它变成真实换行。
  • 文本块内容很大时仍是常量池里的字符串,注意 class 文件大小和启动内存。
// 想得到 JSON 中的 \n 字面量,需要两层转义
String json = """
        {"msg": "line1\\nline2"}
        """;
System.out.println(json);
{"msg": "line1\nline2"}
判断文本块结果是否正确,不要靠肉眼数空格。打印 lines() 并给每行加括号,或者直接对期望字符串做 equals 断言。

版本边界BOUNDARY

这张表比 API 本身更值得记。写代码时先确认你的运行时版本。

特性 / API状态说明
文本块15 正式JEP 378;JDK 13 / 14 预览。
String.formatted15 正式配合文本块做占位替换。
附带空白剥离15 正式最小缩进包含结束分隔符行。
字符串模板21 预览 / 23 撤回17 没有插值能力,不要依赖。
文本块常量15 正式内容为常量时是编译期常量;拼接后不一定。

费曼自检SELF CHECK

先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。

01. 文本块的附带空白是怎么算的?
取所有非空内容行与结束分隔符行的最小缩进,从每行去掉同样多的前导空格,保留相对缩进。结束分隔符越靠左,最小缩进越小,内容保留的空格越多。
02. 文本块里怎么保留行尾空格?
用 \s 显式表示一个空格;直接敲的行尾空格会被当作附带空白剥离。行尾的 \ 则表示不换行,把下一行接上来。
03. 文本块能做字符串插值吗?
不能。文本块只是多行字符串字面量,没有 ${} 插值;需要替换时用 formatted 或 format,模板引擎要另外引入。
04. 为什么文本块里的 JSON 反斜杠要写两层?
文本块仍会处理 Java 转义序列。JSON 需要字面量 \n 时,Java 源码里要写 \\n,否则文本块会先把它变成真实换行。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。