Java 文本块与模式匹配实战——Java 14-21 语法糖的工程化使用场景
Java 文本块与模式匹配实战——Java 14-21 语法糖的工程化使用场景
适读人群:升级到 Java 14+ 但还没充分用上新语法的工程师 | 阅读时长:约13分钟 | 核心价值:这些语法改进的实际用法,让代码更干净
这篇文章集中讲 Java 14 到 21 这几个版本里的"小语法改进"——文本块、instanceof 模式匹配、switch 表达式和模式匹配。
这些特性单独看都不算大,但合在一起,对日常写代码的体验改善挺明显的。我在项目里全面切到 Java 17 之后,很多原来繁琐的写法都清爽了不少。
一、文本块(Text Block,Java 15 正式)
文本块解决了写多行字符串时转义地狱的问题。
场景1:SQL 语句
// Java 15 之前:转义符和拼接,很难读
String sql = "SELECT u.id, u.username, u.email,\n" +
" d.name as dept_name\n" +
"FROM t_user u\n" +
"LEFT JOIN t_department d ON u.dept_id = d.id\n" +
"WHERE u.status = 'ACTIVE'\n" +
" AND u.created_at > :since\n" +
"ORDER BY u.created_at DESC";
// Java 15+ 文本块:直接写,不用转义,格式也清晰
String sql = """
SELECT u.id, u.username, u.email,
d.name as dept_name
FROM t_user u
LEFT JOIN t_department d ON u.dept_id = d.id
WHERE u.status = 'ACTIVE'
AND u.created_at > :since
ORDER BY u.created_at DESC
""";
// 注意:结尾的 """ 的缩进决定了公共缩进的去除量场景2:JSON 字符串
// 测试里构造 JSON 请求体
String requestBody = """
{
"username": "testuser",
"email": "test@example.com",
"roles": ["USER", "VIEWER"]
}
""";
// 带变量的 JSON(用 formatted())
String userId = "12345";
String requestBody2 = """
{
"userId": "%s",
"action": "LOGIN"
}
""".formatted(userId);场景3:HTML 模板(邮件、简单页面)
String emailContent = """
<html>
<body>
<h2>欢迎注册,%s!</h2>
<p>您的账号已创建成功。</p>
<p>点击以下链接激活账号:</p>
<a href="%s">激活账号</a>
</body>
</html>
""".formatted(username, activationUrl);文本块的一个小细节:\ 可以用来续行(不产生换行符),\s 用来保留行尾空格:
String longString = """
这是一段很长的文字,\
需要换行写但不希望字符串里有换行符。\
这三行会被连接成一行。
""";二、instanceof 模式匹配(Java 16 正式)
以前:
// 老写法:判断类型 -> 强转 -> 使用,很繁琐
Object obj = getObject();
if (obj instanceof String) {
String s = (String) obj; // 多此一举的强转
System.out.println(s.length());
}Java 16+ 的模式匹配:
// 新写法:判断的同时绑定变量
if (obj instanceof String s) {
System.out.println(s.length()); // s 直接可用,不用强转
}
// 甚至可以在条件里继续使用
if (obj instanceof String s && s.length() > 5) {
System.out.println("长字符串: " + s);
}实际应用场景:
// 处理不同类型的事件(配合前面说的 Sealed Interface 更好)
public void handleEvent(DomainEvent event) {
if (event instanceof UserCreatedEvent e) {
log.info("用户创建: {}", e.userId());
sendWelcomeEmail(e.email());
} else if (event instanceof OrderPaidEvent e) {
log.info("订单支付: {}", e.orderId());
triggerShipping(e.orderId());
} else if (event instanceof OrderCancelledEvent e) {
log.warn("订单取消: {}, 原因: {}", e.orderId(), e.reason());
refundIfNeeded(e);
}
}三、switch 表达式(Java 14 正式)和模式匹配(Java 21)
Switch 表达式:
// 老写法:switch 语句,容易忘 break
String dayType;
switch (dayOfWeek) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
dayType = "工作日";
break;
case SATURDAY:
case SUNDAY:
dayType = "周末";
break;
default:
dayType = "未知";
}
// 新写法:switch 表达式,箭头语法,不需要 break
String dayType = switch (dayOfWeek) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "工作日";
case SATURDAY, SUNDAY -> "周末";
};
// 枚举的 switch 可以不写 default(编译器会检查穷举)带计算块的 switch(用 yield 返回值):
int discount = switch (userLevel) {
case BRONZE -> 0;
case SILVER -> 5;
case GOLD -> {
// 金牌用户有额外逻辑
int base = 10;
int bonus = user.getYearsOfMembership() > 3 ? 5 : 0;
yield base + bonus; // yield 代替 return
}
case PLATINUM -> 20;
};Switch 的类型模式匹配(Java 21 正式):
// 可以在 switch 里匹配类型
public String formatValue(Object obj) {
return switch (obj) {
case Integer i -> "整数: " + i;
case Long l -> "长整数: " + l;
case Double d -> "浮点数: %.2f".formatted(d);
case String s when s.isEmpty() -> "(空字符串)";
case String s -> "字符串: " + s;
case null -> "(null)";
default -> "其他类型: " + obj.getClass().getSimpleName();
};
}when 关键字(Java 21)是一个守卫条件,可以在模式匹配里加额外的条件判断。
四、Record 的解构(Java 21)
Java 21 引入了 Record 的解构模式,可以在 instanceof 和 switch 里直接提取 Record 的字段:
public record Point(int x, int y) {}
// 解构 Record
Object obj = new Point(3, 4);
if (obj instanceof Point(int x, int y)) {
System.out.println("坐标: (" + x + ", " + y + ")");
}
// 在 switch 里解构
String describe(Object obj) {
return switch (obj) {
case Point(int x, int y) when x == 0 && y == 0 -> "原点";
case Point(int x, int y) when x == 0 -> "在Y轴上";
case Point(int x, int y) when y == 0 -> "在X轴上";
case Point(int x, int y) -> "点(%d, %d)".formatted(x, y);
default -> "非点对象";
};
}五、这些特性在项目里的综合使用
给一个综合使用这些特性的例子:
// 处理支付结果的代码(用了文本块、switch模式匹配、Record解构)
public sealed interface PaymentResult
permits PaymentResult.Success, PaymentResult.Failed, PaymentResult.Pending {}
public record Success(String transactionId, BigDecimal amount) implements PaymentResult {}
public record Failed(String errorCode, String errorMessage) implements PaymentResult {}
public record Pending(String orderId, Duration estimatedWait) implements PaymentResult {}
public String getPaymentSummary(PaymentResult result) {
return switch (result) {
case Success(String txId, BigDecimal amount) -> """
支付成功
交易号:%s
金额:%.2f 元
""".formatted(txId, amount);
case Failed(String code, String msg) when code.startsWith("NETWORK") -> """
网络异常,请重试
错误码:%s
""".formatted(code);
case Failed(String code, String msg) -> """
支付失败
错误码:%s
原因:%s
""".formatted(code, msg);
case Pending(String orderId, Duration wait) -> """
支付处理中
订单号:%s
预计等待:%d 秒
""".formatted(orderId, wait.toSeconds());
};
}这段代码用了文本块写多行字符串、Record + Sealed Interface 定义类型、switch 模式匹配处理各种情况、when 守卫条件细化分支,整体代码非常紧凑,而且有编译器的穷举检查保证不会漏处理某种情况。
这些语法改进的共同特点是:没有引入新的编程范式,只是让已有的模式写起来更简洁。升级成本低,收益直接可见,是我比较推荐的"先升级先用"的特性。
下一篇写 Java FFI(Panama),这个更小众,但如果你有 Java 调 C 库的需求,Panama 真的大幅降低了这件事的难度。
