Java 文本块 Text Blocks 详解:高效处理多行字符串的优雅方案

|

我们最近启动的新项目,统一都用上了 JDK21。说起来,现在都 2025 年了,JDK24都出来了,我们还在用JDK21,更魔幻的是有的人还把JDK8引入的特性当成新特性…

其实有些早就发布、但特别实用的新特性,至今还有很多人没真正用过。今天就想聊一个对写代码挺有帮助的功能:文本块。熟悉的朋友可能早已用得顺手了,不熟的朋友可能连它长什么样都还不知道。

对 Java 开发者来说,多行字符串一直是个难题。无论是写 SQL、拼 JSON、嵌 HTML,还是搞个配置文件,只要一多行,屏幕上立马就堆满了拼接符、转义字符,还有那乱七八糟的缩进,改起来很麻烦。

现在,Java 给我们带来了一个更优雅的解决方案: Text Blocks(文本块)。有了它,处理多行字符串终于不再那么头疼,代码更清爽,可读性也高了不少。

1. 什么是 Text Blocks(文本块)?

Text Blocks 最早是在 Java 13 以预览功能亮相,Java 14 做了些改进,到 Java 15 正式成为标准。它的核心目标很简单:让我们可以直接、干净地写多行字符串,不再被各种引号、加号和转义搞得焦头烂额。

它的语法也很直观:用三个双引号(""")包裹内容,就能写出结构清晰、格式保持原样的多行文本。不用拼、不用转义,直接贴上去就行。

以下是一个最基本的文本块的使用示例:

1
2
3
4
5
String textBlock = """
这是一个文本块
跨越多行
保留了格式
""";

主要结构如下:

起始定界符:必须是三个双引号("""),而且后面要紧跟一个换行,不能省。

内容部分:可以写在多行上,怎么换行、怎么缩进,全都按你写的来保留原样。

结束定界符:同样是 """,可以直接写在内容最后一行的末尾,也可以另起一行单独写,灵活处理。

以下都是语法正确的写法:

1
2
3
4
5
6
7
8
9
10
11
12
// 合法写法:结束定界符单独成行
String example1 = """
Hello World
""";

// 合法写法:结束定界符与内容在同一行
String example2 = """
Hello World""";

// 合法写法:空文本块
String empty = """
""";

2. 基本用法

2.1. JSON 更自然地嵌入代码

传统写法,用传统方式拼 JSON,换行全靠 \n,字符串拼接像堆积木,格式难对齐,改字段尤其麻烦,稍不留神就漏了逗号或引号:

1
2
3
4
5
6
7
String json = "{\n" +
" \"id\": 101,\n" +
" \"name\": \"Wireless Mouse\",\n" +
" \"price\": 29.99,\n" +
" \"inStock\": true,\n" +
" \"tags\": [\"electronics\", \"accessories\", \"usb\"]\n" +
"}";

使用 Text Blocks 之后,看看这个版本,和你在 Postman 或 JSON 编辑器里看到的格式几乎一模一样,结构清晰、一眼能看懂。复制、调试、修改都方便,特别适合构造请求体、模拟响应或做配置模板:

1
2
3
4
5
6
7
8
9
String json = """
{
"id": 101,
"name": "Wireless Mouse",
"price": 29.99,
"inStock": true,
"tags": ["electronics", "accessories", "usb"]
}
""";

2.2. HTML Templates

传统写法,拼 HTML 的方式,不仅看起来乱,每一行都被双引号框着、加号连着,还容易因为忘记转义或少拼一个变量而报错。页面结构根本看不清,调试起来很费劲:

1
2
3
4
5
6
7
8
9
10
String html = "<html>\n" +
" <head>\n" +
" <title>Product Detail</title>\n" +
" </head>\n" +
" <body>\n" +
" <h1>" + productName + "</h1>\n" +
" <p>价格:¥" + price + "</p>\n" +
" <p>库存状态:" + stockStatus + "</p>\n" +
" </body>\n" +
"</html>";

使用 Text Blocks 之后,HTML 写得像模板一样自然,变量通过 .formatted() 插入,逻辑清晰、结构分明。无论是动态生成页面、预览邮件内容,还是写测试输出,这种方式都省心不少:

1
2
3
4
5
6
7
8
9
10
11
12
String html = """
<html>
<head>
<title>Product Detail</title>
</head>
<body>
<h1>%s</h1>
<p>价格:¥%s</p>
<p>库存状态:%s</p>
</body>
</html>
""".formatted(productName, price, stockStatus);

2.3. SQL 查询

虽然很少在代码中这么写sql,但是还是写下这种用法。

传统写法,拼接符满天飞,格式乱糟糟,看久了眼疼。每次改列名或者调整顺序都得小心翼翼:

1
2
3
4
5
6
7
String sql = "SELECT customer_id, COUNT(order_id) AS order_count, " +
" SUM(total_amount) AS total_spent " +
"FROM orders " +
"WHERE order_date BETWEEN ? AND ? " +
" AND status = 'COMPLETED' " +
"GROUP BY customer_id " +
"ORDER BY total_spent DESC";

使用 Text Blocks 之后,整个查询语句就像直接复制粘贴自数据库客户端一样直观,结构清晰,不再被格式和语法干扰,大大提升了可读性和维护效率。

1
2
3
4
5
6
7
8
9
String sql = """
SELECT customer_id, COUNT(order_id) AS order_count,
SUM(total_amount) AS total_spent
FROM orders
WHERE order_date BETWEEN ? AND ?
AND status = 'COMPLETED'
GROUP BY customer_id
ORDER BY total_spent DESC
""";

3. 高级特性与能力

Text Blocks 搭配字符串格式化,让模板更灵活

Text Blocks 可以轻松和 String.format().formatted() 方法结合,用来动态插入变量,非常适合写邮件模板、系统通知等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用 String.format() 拼接用户通知
String template = """
亲爱的 %s:

您的订单(编号:%s)已成功付款 ¥%.2f。
我们将在 %s 前完成发货,请留意物流信息。

感谢您的支持!
—— 商城团队
""";

String message = String.format(template, "李明", "A20230721", 259.80, "2025-07-25");

// 使用 formatted()(Java 13+ 推荐方式)
String quickMsg = """
您好 %s,
您当前积分为:%d 分。
""".formatted("小张", 820);

Text Blocks 中的特殊字符处理

大多数情况下你不需要再用 \n\" 这些转义符,但有时仍然会用到,比如需要制表符、斜杠或三引号的情况:

1
2
3
4
5
6
7
String log = """
第1行
第2行,含制表符:\t(对齐用)
第3行,带引号:"不需转义"
第4行,路径示例:C:\\Users\\Documents
第5行,包含三引号:\"""
""";

缩进处理

Text Blocks 会自动识别和剥离共同缩进,让你专注内容本身,不用手动对齐每一行:

1
2
3
4
5
6
7
8
9
10
11
12
public class DemoIndent {
public void show() {
// 缩进以最少缩进行为基准,自动裁剪左侧空格
String snippet = """
<div>
<p>Hello, Text Blocks!</p>
</div>
""";

System.out.println(snippet); // 输出时仍保留相对缩进结构
}
}

使用 indent() 方法手动添加缩进

虽然 Text Blocks 会自动处理缩进,但你也可以用 .indent() 增加额外缩进(比如嵌入模板、展示源码等场景):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String content = """
标题:使用 Text Blocks
内容行 1
内容行 2(有缩进)
结尾
""";

// 增加整体缩进
String indented = content.indent(2);

// 连续链式操作
String result = """
示例开始:
这是一段嵌入内容
示例结束。
""".indent(4);

高级用法与最佳实践

1. Text Blocks 用于配置文件(如 YAML)

Text Blocks 非常适合用于表示结构化配置,如 YAML、Properties 等,避免了繁琐的拼接和缩进混乱:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String yamlConfig = """
server:
port: 8080
host: 127.0.0.1

datasource:
driver: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shop
username: ${DB_USER}
password: ${DB_PASSWORD}

logging:
level:
root: INFO
com.example: DEBUG
""";

2. 测试场景中构造结构化数据

Text Blocks 在编写测试时,可以快速构造清晰的 JSON 或 XML,提升可读性和可维护性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void shouldParseUserListCorrectly() {
String testJson = """
{
"users": [
{
"id": 101,
"name": "张三",
"email": "zhangsan@example.com",
"enabled": true
},
{
"id": 102,
"name": "李四",
"email": "lisi@example.com",
"enabled": false
}
]
}
""";

JsonObject obj = JsonParser.parseString(testJson).getAsJsonObject();
// 执行断言测试
}

3. 正则表达式模式

当用 Text Blocks 写正则表达式时,要注意结尾的换行可能影响匹配效果。对于单行模式,建议使用 .strip() 清除末尾换行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class RegexToolkit {

// 示例:用于验证手机号
private static final String PHONE_REGEX = """
^1[3-9]\\d{9}$
""".strip(); // 去掉 Text Block 尾部的换行符

// ❌ 如果不 strip,会包含 \n 导致匹配失败

// 多行正则示例(用于解析日志)—— strip 可选
private static final String LOG_PATTERN = """
^(?<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\s+
(?<level>INFO|WARN|ERROR)\\s+
(?<message>.*)$
""".strip();

// 使用正则宽松模式(verbose)时,不需要 strip
private static final String DATE_REGEX = """
(?x) # 开启注释模式
^ # 行首
(?<year>\\d{4}) # 年
- # 分隔符
(?<month>\\d{2}) # 月
- # 分隔符
(?<day>\\d{2}) # 日
$ # 行尾
""";

public static void compareStripEffect() {
String raw = """
^hello$
""";
String cleaned = raw.strip();

System.out.println("原始长度:" + raw.length()); // 包含换行
System.out.println("strip 后长度:" + cleaned.length());

System.out.println("未 strip 匹配:" + Pattern.compile(raw).matcher("hello").matches()); // false
System.out.println("已 strip 匹配:" + Pattern.compile(cleaned).matcher("hello").matches()); // true
}
}

什么时候该用 .strip()

建议使用 .strip() 的场景:

  • 构建 单行正则 时,为了避免尾部换行影响匹配
  • 需要精确控制字符串的场景(如配置 key、命令行片段)

什么时候不需要 .strip()

  • 正则开启了宽松模式 (?x),会忽略空格和换行
  • 正则本身就是多行模式,结构依赖换行存在

注意事项:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 错误:结尾带换行,导致匹配失败
String badRegex = """
^\\d{6}$
""";

Pattern.compile(badRegex).matcher("123456").matches(); // false!

// 正确:去掉末尾换行
String goodRegex = """
^\\d{6}$
""".strip();

Pattern.compile(goodRegex).matcher("123456").matches(); // true!

正则场景用 Text Blocks 要特别小心尾部空白,.strip() 是常用“补丁”。

性能考虑

Text Blocks 在编译阶段就会被转换为普通的 String 实例,因此在运行时几乎没有任何性能损耗。不过,使用时还是有些细节值得注意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 推荐:定义为静态常量,只初始化一次
private static final String SQL_TEMPLATE = """
SELECT id, name, price FROM products
WHERE category = ? AND status = ?
""";

// 不推荐:每次方法调用都重新构建文本块
public String buildQuery(String category, String status) {
return """
SELECT id, name, price FROM products
WHERE category = '%s' AND status = '%s'
""".formatted(category, status); // 每次都重新创建字符串
}

// 更优方案:使用静态模板 + formatted 插入参数
public String buildQuery(String category, String status) {
return SQL_TEMPLATE.formatted(category, status);
}

为了提高性能,这里建议把模板定义为常量,避免在热点方法中频繁创建相同字符串,尤其是数据库查询、日志格式、消息内容等。

Text Blocks ≠ 原始字符串字面量

有些人误以为 Text Blocks 是“原始字符串”,但其实并不是。Text Blocks 仍然会解析转义字符,和真正意义上的“原样输出”并不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Text Blocks 中的转义字符依然生效
String 示例1 = """
第一行\n
第二行\t缩进
"""; // 输出中会真正换行或加入制表符

// Windows 路径依然需要双反斜杠
String 路径 = """
D:\\Work\\Docs\\report.pdf
""";

// 好消息是,大部分引号和换行现在可以直接写,不必转义
String json输出 = """
{
"name": "小王",
"status": "已完成",
"备注": "文件位于 \"D:\\\\Files\""
}
""";

总结

Text Blocks 是 Java 在多行字符串处理方面的一次重大升级。它不仅让代码更整洁、易读、更少出错,更在日常开发中节省了大量维护和格式调整的时间。

为什么推荐使用它呢?主要是基于以下几个优点来考虑的:

  • 少写一堆反斜杠:写 JSON、HTML 不再满屏 \"\n
  • 结构一目了然:特别适合写 SQL、配置、模板类的内容,层次分明。
  • 维护调试更轻松:复制粘贴就能直接跑,不容易出错,改起来也省心。
  • 格式化更自然:配合 .formatted() 使用,方便构建带参数的文本块。

不过使用时要注意以下几点:

  • 搞清楚 开始和结束的定界符规则,别被格式坑了。
  • 缩进会自动处理,它不是原样输出,这点需要留意。
  • 合理用 .strip().indent(),能避免多余的空格或换行带来的小麻烦。
  • 尽量不要在 Text Blocks 里拼字符串,保持内容独立更好管理。

还在用旧版 Java?是时候升级了。

你们团队还没人用文本块?这篇文章值得收藏,试过就回不去了。

别再用加号一条条拼字符串了,换种写法,代码清爽,维护也轻松。

想了解更多实用技巧,欢迎关注「Java架构杂谈」。我们下期见~