Java 的文本块(Text Blocks)是什么?怎么处理缩进和换行?
简化版
文本块(Text Blocks,Java 15 正式引入)是多行字符串的简洁写法——用三个双引号 """ 包裹一段跨多行的文本,不用再写一堆 \n 换行符和 + 拼接。特别适合写 SQL、JSON、HTML、XML 等多行内容,可读性大幅提升(所见即所得)。它自动处理换行(每行末尾自动加 \n),并有一套「去除公共缩进」的规则——编译器会自动删掉每行前面的公共空白(以内容最左边或结束 """ 的位置为基准),让你能在代码里正常缩进而不把缩进带进字符串。它是编译期语法糖,运行时就是普通的 String。
详细版
传统多行字符串 vs 文本块:
// 传统写法:\n 换行 + + 拼接,难读难维护
String json = "{\n" +
" \"name\": \"Tom\",\n" +
" \"age\": 18\n" +
"}";
// 文本块:所见即所得
String json = """
{
"name": "Tom",
"age": 18
}""";
// 内容就是花括号里那几行,换行自动处理,引号不用转义
核心规则:
| 规则 | 说明 |
|---|---|
开始 """ 后必须换行 | """ 后面必须紧跟换行,内容从下一行开始 |
| 自动换行 | 每行内容末尾自动加 \n(除了最后一行看 """ 位置) |
| 去除公共缩进 | 编译器删除所有行的公共前导空白(“附带缩进”) |
| 引号不用转义 | 里面的 " 不用写成 \"(除非连续三个 ") |
结尾 """ 位置影响缩进和末尾换行 | """ 越靠左,保留的缩进越少 |
去除公共缩进的机制:
String text = """
Hello
World
""";
// 三行内容前面都有 8 个空格(代码缩进)
// 编译器找"最小公共缩进"(这里所有行 + 结束 """ 的位置)→ 删掉这些公共空白
// 结果:"Hello\nWorld\n"(前导空格被去掉了)
几个控制符(Java 14+):
String s = """
很长的一行想在代码里换行但\
不想真的换行"""; // 行尾 \ 表示"这里不换行"(续行)
String t = """
末尾要保留空格 \s
"""; // \s 表示一个空格,防止行尾空格被去除
⚠️ 文本块去除的是「公共」前导空白(附带缩进 incidental whitespace),保留「相对」缩进(essential whitespace)。判断基准是「所有非空行的最小缩进」和「结束
"""的位置」——取两者中更靠左的。所以你可以在代码里正常缩进文本块(为了代码美观),编译器会自动把这些「为了代码对齐的缩进」去掉,只保留文本本身该有的相对缩进(如 JSON 内部的层级缩进)。
完整版教学
一、文本块解决什么:多行字符串的痛苦
在文本块之前,Java 写多行字符串非常痛苦——要手动加 \n 换行、用 + 拼接、还要转义引号:
// 写一段 SQL,传统方式:
String sql = "SELECT u.name, u.age\n" +
"FROM user u\n" +
"WHERE u.status = 'ACTIVE'\n" +
" AND u.age > 18\n" +
"ORDER BY u.age DESC";
// 问题:满屏的 \n 和 +,看不清 SQL 原貌,容易漏空格/写错,维护困难
痛点:① \n 和 + 淹没了真正的内容(看 SQL 要在噪音里找);② 容易出错(漏个空格、少个 \n);③ 修改麻烦(每行都要维护引号和加号)。对于 SQL、JSON、HTML 这些「本身就是多行文本」的内容,这种写法尤其难受。文本块就是来终结这个痛苦的——让多行字符串在代码里「所见即所得」,你在代码里看到什么样,字符串就是什么样。
二、基本语法与自动换行
文本块用三个双引号 """ 开始和结束,中间是文本内容:
String sql = """
SELECT u.name, u.age
FROM user u
WHERE u.status = 'ACTIVE'
ORDER BY u.age DESC""";
两个基本规则:① 开始的 """ 后必须换行(内容从下一行开始,""" 那一行不能有内容);② 每行内容末尾自动加换行符 \n(你不用写 \n)。所以上面的 sql 实际值是 SELECT...\nFROM...\nWHERE...\nORDER BY...(每行之间有换行)。
引号处理也更友好:里面的单个/双个 " 不用转义(WHERE name = "Tom" 直接写),只有连续三个 """ 才需要处理(会和结束符冲突)。这让写 JSON(满是双引号)变得轻松——不用把每个 " 都写成 \"。理解「""" 包裹、后必换行、自动加 \n、引号免转义」,就掌握了文本块的基本用法。
三、核心难点:去除公共缩进
文本块最需要理解的是「缩进处理」——它要解决一个矛盾:你想在代码里给文本块缩进(为了代码美观对齐),但又不想把这些代码缩进带进字符串。文本块的解法是「去除公共前导空白」:
String text = """
Hello
World
""";
代码里三行内容前面都有 8 个空格(因为代码缩进):
Hello ← 8 空格 + Hello
World ← 8 空格 + World
(结束 """ 也在 8 空格位置)
编译器的处理:
1. 找所有行(含结束 """ 那行)的"最小公共缩进" = 8 个空格
2. 把每行前面这 8 个公共空格删掉(这叫"附带缩进 incidental whitespace")
3. 结果:"Hello\nWorld\n"(没有前导空格了)
关键概念:「附带缩进」(incidental whitespace,为代码对齐而加的公共缩进)被去除,「本质缩进」(essential whitespace,文本本身该有的相对缩进)被保留。这让你能在代码里自由缩进文本块(保持代码整洁),而字符串内容不受影响。这就是文本块「所见即所得」还能「代码里正常缩进」的秘密。
四、结束 """ 的位置:控制缩进基准
「最小公共缩进」的基准里有个关键角色——结束的 """ 的位置也参与计算,且它能用来精确控制保留多少缩进:
// 情况1:结束 """ 和内容对齐(都缩进 8 格)
String a = """
Hello
""";
// 公共缩进 = 8(内容和 """ 都是 8)→ 去掉 8 → "Hello\n"
// 情况2:结束 """ 更靠左(缩进 0)
String b = """
Hello
""";
// 公共缩进 = 0(结束 """ 在最左,成了基准)→ 内容前的 8 空格被保留 → " Hello\n"
规律:结束 """ 越靠左,去除的公共缩进越少(保留的前导空格越多)。因为它参与「最小公共缩进」的计算,它的位置成了「缩进删除的下界」。所以要精确控制文本块保留多少前导缩进,就调整结束 """ 的位置。实践中一般让结束 """ 和内容左对齐(去掉所有代码缩进,得到干净的文本),或按需靠左保留部分缩进。理解「结束 """ 位置决定缩进基准」,就能精确控制文本块的缩进。
五、末尾换行与续行符
两个常见的控制需求:控制末尾有没有换行、长行在代码里折行但不真的换行:
// 末尾换行:由结束 """ 的位置决定
String withNewline = """
Hello
"""; // 结束 """ 单独一行 → "Hello\n"(末尾有换行)
String noNewline = """
Hello"""; // 结束 """ 紧跟内容 → "Hello"(末尾无换行)
// 续行符 \:行尾的 \ 表示"这里不换行"(把物理上的两行连成逻辑上的一行)
String longLine = """
这是很长的一行,在代码里\
换行显示但字符串里不换行""";
// 结果:"这是很长的一行,在代码里换行显示但字符串里不换行"(\ 消除了换行)
// \s:表示一个空格,防止行尾空格被自动去除(尾部空格默认会被删)
String s = """
行尾要保留空格\s
""";
要点:末尾换行看结束 """ 是否单独成行(单独一行则有 \n,紧跟内容则无);续行符 \(行尾)把两行连成一行(用于长行在代码里折行显示但不真换行);\s 保留一个空格(因为文本块默认会去除每行尾部的空白)。这几个控制符让文本块能精确表达各种换行/空格需求。
六、适用场景与注意点
文本块最闪光的场景是「内嵌的多行结构化文本」:
| 场景 | 好处 |
|---|---|
| SQL 语句 | 所见即所得,一眼看清 SQL 结构,不用拼 \n |
| JSON / XML | 引号不用转义,层级缩进清晰 |
| HTML 模板 | 直接写标签,可读性高 |
| 多行提示文本、帮助信息 | 排版直观 |
注意点:① 文本块运行时就是普通 String(编译期就处理成带 \n 的字符串,运行时无差别,可以正常做 +、.length() 等);② 不适合频繁拼接变量——文本块本身不支持插值(Java 没有字符串模板,String Templates 曾预览但被移除),要插入变量还是用 String.format 或 +;③ 注意平台换行符——文本块统一用 \n,不受平台影响(不会因 Windows/Linux 而不同)。总之,文本块是「写多行静态文本」的利器,但不是「动态拼接」的方案。用对场景(写死的 SQL/JSON/HTML 模板),它能大幅提升可读性。
记忆钩子:「文本块(Java 15)=
"""包裹的多行字符串,写 SQL/JSON/HTML 所见即所得;后必换行、每行自动加 \n、引号免转义;去除『附带缩进』(公共前导空白)保留『本质缩进』,基准是最小公共缩进 + 结束 """ 位置;末尾换行看 """ 是否单独成行,\ 续行、\s 保留空格;运行时就是普通 String」。
七、常见误区与追问
- 误区:文本块是一种新的字符串类型。 不是——它编译后就是普通 String,运行时和用
\n++拼出来的字符串完全一样,只是写法更简洁。 - 误区:文本块里的所有缩进都会保留。 不会——编译器去除「公共前导空白(附带缩进)」,只保留「相对缩进(本质缩进)」,让你能在代码里正常缩进而不带进字符串。
- 误区:文本块支持插入变量。 不支持插值——文本块是静态文本,插变量还得用 String.format 或 +;Java 的字符串模板(String Templates)曾预览但已被移除。
- 误区:
"""后可以直接跟内容。 不行——开始的"""后必须换行,内容从下一行开始;"""那一行不能有内容。 - 追问:文本块怎么控制末尾有没有换行? 看结束
"""的位置——单独成一行则末尾有\n,紧跟最后一行内容则末尾无换行。 - 追问:怎么去除或保留文本块的前导缩进? 由「最小公共缩进」决定,基准包括所有内容行和结束
"""的位置——结束"""越靠左保留的缩进越多;让它和内容左对齐可去掉所有代码缩进。 - 追问:文本块行尾的
\和\s是什么?\(行尾续行符)表示这里不换行、把两行连成一行;\s表示一个空格,用于保留默认会被去除的行尾空格。
八、加强记忆
文本块(Text Blocks,Java 15 正式)是多行字符串的简洁写法——用 """ 包裹跨多行的文本,专治 SQL/JSON/HTML/XML 这类多行内容的书写痛苦(不用再满屏 \n 和 +、引号也免转义),所见即所得。基本规则:开始 """ 后必须换行、每行末尾自动加 \n、引号免转义。核心难点是缩进处理——编译器去除「附带缩进」(为代码对齐加的公共前导空白 incidental whitespace)、保留「本质缩进」(文本本身的相对缩进 essential whitespace),判断基准是「所有内容行 + 结束 """ 位置」的最小公共缩进,所以你能在代码里正常缩进而不把缩进带进字符串(结束 """ 越靠左保留缩进越多)。控制符:末尾换行看 """ 是否单独成行、行尾 \ 续行(两行连一行)、\s 保留一个空格。它是编译期语法糖,运行时就是普通 String(可正常操作),但不支持变量插值(插变量用 String.format/+),适合写静态多行文本而非动态拼接。一句话「""" 多行字符串所见即所得、后必换行自动加 \n、去公共缩进保相对缩进、结束 """ 位置控缩进、运行时是普通 String 不支持插值」。