QUICKREF · LANGUAGE · JEP 378
文本块:多行字符串的可读写法
JDK 17 · 速查主题 05 / 06 · 预计 15 分钟
一句话文本块把 SQL、JSON、HTML 从字符串拼接里解放出来,但它的规则不是「原样保留」:编译器会去掉附带空白,行尾空格和缩进都会影响结果。
学完你要能自己回答
- 附带空白(incidental whitespace)是怎么计算的?结束分隔符为什么影响结果?
- \s 与行尾 \ 分别解决什么问题?
- 文本块能做什么、不能做什么?为什么它不能替代模板引擎?
语法与附带空白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.formatted | 15 正式 | 配合文本块做占位替换。 |
| 附带空白剥离 | 15 正式 | 最小缩进包含结束分隔符行。 |
| 字符串模板 | 21 预览 / 23 撤回 | 17 没有插值能力,不要依赖。 |
| 文本块常量 | 15 正式 | 内容为常量时是编译期常量;拼接后不一定。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。
01. 文本块的附带空白是怎么算的?
取所有非空内容行与结束分隔符行的最小缩进,从每行去掉同样多的前导空格,保留相对缩进。结束分隔符越靠左,最小缩进越小,内容保留的空格越多。
02. 文本块里怎么保留行尾空格?
用 \s 显式表示一个空格;直接敲的行尾空格会被当作附带空白剥离。行尾的 \ 则表示不换行,把下一行接上来。
03. 文本块能做字符串插值吗?
不能。文本块只是多行字符串字面量,没有 ${} 插值;需要替换时用 formatted 或 format,模板引擎要另外引入。
04. 为什么文本块里的 JSON 反斜杠要写两层?
文本块仍会处理 Java 转义序列。JSON 需要字面量 \n 时,Java 源码里要写 \\n,否则文本块会先把它变成真实换行。
学习进度
记录保存在本机浏览器 · 不上传