1. 概述
在之前的教程中,我们已经了解了如何在任意 Java 版本中使用多行字符串(比如通过拼接或 StringBuilder)。
本文将深入讲解 Java 15 正式引入的 Text Blocks 功能,它让我们声明多行字符串的方式更加简洁高效,尤其适合嵌入 HTML、JSON、SQL 等结构化文本。
✅ 从 Java 15 开始,Text Blocks 已是标准功能。
⚠️ 在 Java 13 和 14 中属于预览特性(preview feature),需手动开启。
2. 基本用法
Text Blocks 使用三个双引号 """
开始,后跟可选空格和换行符。语法非常直观:
String example = """
Example text""";
📌 关键点:
- ✅ 结果类型依然是
String
,只是字面量写法变了。 - ✅ 可自由使用换行和双引号,无需转义。
- ✅ 自动处理首行缩进和首尾空白,提升可读性。
举个实际例子:写一段 HTML 片段再也不用拼接了:
String html = """
<html>
<body>
<p>Hello "world"!</p>
</body>
</html>""";
对比传统写法:
String htmlOld = "<html>\n" +
" <body>\n" +
" <p>Hello \"world\"!</p>\n" +
" </body>\n" +
"</html>";
一眼就能看出,Text Blocks 更清晰、不易出错,尤其在复杂字符串场景下简直是“降维打击”。
3. 缩进处理机制
这是很多人刚用 Text Blocks 时容易踩坑的地方:编译器会自动去除“公共最小缩进”。
工作原理
- 扫描所有非空行,找出最左边的缩进量(以空格计)
- 将整个文本块整体左移该缩进量
- 第一个换行也会被自动去除
看这个例子:
public String getBlockOfHtml() {
return """
<html>
<body>
<span>example text</span>
</body>
</html>""";
}
其中 <html>
行有 12 个空格缩进,且是所有非空行中最少的,因此编译器会把每行都左移 12 个空格。
最终生成的字符串等价于:
"<html>\n\n <body>\n <span>example text</span>\n </body>\n</html>"
我们可以通过单元测试验证:
@Test
void givenAnOldStyleMultilineString_whenComparing_thenEqualsTextBlock() {
String expected = "<html>\n"
+ "\n"
+ " <body>\n"
+ " <span>example text</span>\n"
+ " </body>\n"
+ "</html>";
assertThat(subject.getBlockOfHtml()).isEqualTo(expected);
}
如何保留特定缩进?
如果你想保留比“最小缩进”更多的空格,可以在某行减少缩进来“锚定”更小的公共缩进。
例如:
public String getNonStandardIndent() {
return """
Indent
""";
}
这里 " "
有 12 个空格,但内容行只有 4 个,所以最小缩进是 4。结果为:
" Indent\n"
✅ 小技巧:如果想让内容完全顶格,可以在最后一行加一个空行并缩进回来:
return """
line1
line2
""";
// 结果:"line1\nline2\n"
4. 转义规则
虽然 Text Blocks 减少了转义需求,但某些场景仍需手动控制。
4.1 双引号转义
✅ 在 Text Blocks 中,双引号无需转义!
String text = """
He said: "Hello, world!"
""";
❌ 但如果你写了三个双引号 """
,就必须转义其中一个,否则会被误认为是结束符:
public String getTextWithEscapes() {
return """
"fun" with
whitespace
and other escapes \"""
""";
}
⚠️ 注意:虽然可以写 \"
,但在 Text Blocks 中这样做属于反模式——既然提供了更优雅的写法,何必自找麻烦?
4.2 换行符转义
✅ 默认情况下,换行符无需转义,会自动归一化为 \n
。
⚠️ **即使源文件使用 Windows 换行符(\r\n
),Text Blocks 也只会生成 \n
**。
如果需要保留 \r
,必须显式写出:
public String getTextWithCarriageReturns() {
return """
separated with\r
carriage returns""";
}
测试验证:
@Test
void givenATextWithCarriageReturns_thenItContainsBoth() {
assertThat(subject.getTextWithCarriageReturns())
.isEqualTo("separated with\r\ncarriage returns");
}
忽略物理换行(逻辑合并)
有时候代码里为了可读性想把长字符串拆行,但希望运行时合并成一行。可以用 \
忽略换行:
public String getIgnoredNewLines() {
return """
This is a long test which looks to \
have a newline but actually does not""";
}
✅ 实际结果是一整行:
@Test
void givenAStringWithEscapedNewLines_thenTheResultHasNoNewLines() {
String expected = "This is a long test which looks to have a newline but actually does not";
assertThat(subject.getIgnoredNewLines())
.isEqualTo(expected);
}
4.3 空格转义
✅ 编译器默认会删除所有行尾空格(trailing spaces),这是为了防止无意引入空白字符。
但从 Java 14 预览开始,支持使用 \s
保留空格:
public String getEscapedSpaces() {
return """
line 1·······
line 2·······\s
""";
}
📌 说明:上面的 ·
是为了可视化空格而添加的标记,实际代码中是空格。
结果:
"line 1\nline 2 \n"
解释:
- 第一行末尾的空格被删掉了
- 第二行用了
\s
,所以前面的所有空格都被保留
⚠️ 注意:\s
不代表“一个空格”,而是“保留此处之前的连续空格”,常用于格式对齐或模板填充。
5. 字符串格式化
Java 15 还为 String
类新增了一个实用方法:.formatted()
,可以直接在文本块上调用变量替换。
public String getFormattedText(String parameter) {
return """
Some parameter: %s
""".formatted(parameter);
}
等价于:
String.format("""
Some parameter: %s
""", parameter);
但 .formatted()
更简洁,链式调用也更自然,推荐优先使用。
6. 总结
Text Blocks 虽然不是颠覆性特性,但在日常开发中极大提升了代码可读性和维护性,尤其是在处理:
- SQL 查询语句
- JSON 模板
- HTML/XML 片段
- Shell 脚本嵌入
✅ 推荐使用场景:
场景 | 推荐程度 |
---|---|
多行 SQL | ⭐⭐⭐⭐⭐ |
内联 JSON | ⭐⭐⭐⭐☆ |
日志模板 | ⭐⭐⭐☆☆ |
简单拼接 | ⭐☆☆☆☆(没必要) |
🚀 小建议:升级到 Java 15+ 后,遇到多行字符串优先考虑 Text Blocks,简单粗暴又安全。
完整示例代码已托管至 GitHub:https://github.com/baomidou/tutorials/tree/master/core-java-15