写Java代码的时候,最烦的就是字符串拼接了。以前用+号拼接,代码写得又臭又长,特别是那种多行字符串,看着就头疼。后来有了StringBuilder和String.format(),稍微好点,但还是不够优雅。鹏磊我之前写日志、生成SQL、拼接URL这些场景,每次都得写一堆+号,代码可读性差,还容易出错。
现在好了,JDK 24终于引入了字符串模板(String Templates)这个特性,虽然还是预览版,但已经能解决不少问题了。这个特性让字符串拼接变得简洁明了,代码写起来也顺手多了。兄弟们别磨叽,咱这就开始整活,把这个特性给整明白。
什么是字符串模板
先说说啥是字符串模板。字符串模板是JDK 24引入的一个预览特性,用来简化字符串拼接和格式化。它通过模板表达式(Template Expression)的方式,让你可以在字符串里直接嵌入变量和表达式,不用再写一堆+号了。
字符串模板的核心思想是:提供一个统一的机制来处理字符串拼接,同时保证安全性(自动转义特殊字符)。它使用模板处理器(Template Processor)来处理模板表达式,最常用的是STR处理器。
以前拼接字符串,得这么写:
// 老写法,用+号拼接
String name = "鹏磊";
int age = 30;
String city = "北京";
String message = "我叫" + name + ",今年" + age + "岁,住在" + city + "。";
System.out.println(message); // 输出: 我叫鹏磊,今年30岁,住在北京。
现在用字符串模板,直接就能这么写:
// 新写法,用字符串模板
String name = "鹏磊";
int age = 30;
String city = "北京";
String message = STR."我叫\{name},今年\{age}岁,住在\{city}。";
System.out.println(message); // 输出: 我叫鹏磊,今年30岁,住在北京。
是不是清爽多了?代码量少了,逻辑也更清晰,不用再搞那些+号的破事了。
字符串模板的语法
字符串模板的语法很简单,主要就是模板表达式。模板表达式由三部分组成:模板处理器、点号(.)、模板字符串。
基本语法
模板表达式的格式是:处理器.模板字符串。模板字符串用双引号或三引号包裹,里面用\{表达式}来嵌入变量或表达式。
// 基本语法示例
String name = "Alice";
int score = 95;
// 使用STR处理器,\{name}和\{score}会被替换成实际值
String result = STR."学生\{name}的分数是\{score}分";
System.out.println(result); // 输出: 学生Alice的分数是95分
模板字符串格式
模板字符串可以用双引号或三引号(用于多行字符串):
// 单行模板字符串
String singleLine = STR."Hello, \{name}!";
// 多行模板字符串(用三引号)
String multiLine = STR."""
这是第一行: \{name}
这是第二行: \{score}
这是第三行: 结束
""";
嵌入表达式
模板字符串里可以嵌入任何Java表达式,不只是变量:
// 嵌入方法调用
String result1 = STR."当前时间: \{LocalDateTime.now()}";
// 嵌入算术表达式
int a = 10;
int b = 20;
String result2 = STR."\{a} + \{b} = \{a + b}"; // 输出: 10 + 20 = 30
// 嵌入条件表达式
int age = 25;
String result3 = STR."\{age >= 18 ? "成年" : "未成年"}"; // 输出: 成年
// 嵌入对象方法调用
String name = " Java ";
String result4 = STR."处理后的名字: \{name.trim().toUpperCase()}"; // 输出: 处理后的名字: JAVA
STR处理器
STR是JDK 24提供的一个内置模板处理器,全称是StringTemplate.Processor。它是最常用的处理器,用来处理字符串模板表达式。
STR的基本使用
STR处理器会自动把模板表达式转换成字符串,处理嵌入的变量和表达式:
// STR处理器的基本使用
String name = "鹏磊";
int age = 30;
// STR处理器处理模板表达式
String message = STR."我叫\{name},今年\{age}岁";
System.out.println(message); // 输出: 我叫鹏磊,今年30岁
STR的自动转义
STR处理器会自动转义特殊字符,防止注入攻击。这对生成SQL、HTML、JSON这些场景特别有用:
// STR自动转义示例
String userInput = "'; DROP TABLE users; --"; // 恶意输入
// 老写法,容易SQL注入
String sql1 = "SELECT * FROM users WHERE name = '" + userInput + "'";
// 危险!可能被注入攻击
// 新写法,STR自动转义
String sql2 = STR."SELECT * FROM users WHERE name = '\{userInput}'";
// 安全!STR会自动转义特殊字符
STR处理多行字符串
STR处理器支持多行字符串,用三引号包裹:
// 多行字符串模板
String name = "鹏磊";
String email = "[email protected]";
int age = 30;
String profile = STR."""
姓名: \{name}
邮箱: \{email}
年龄: \{age}
""";
System.out.println(profile);
// 输出:
// 姓名: 鹏磊
// 邮箱: [email protected]
// 年龄: 30
其他模板处理器
除了STR,JDK 24还提供了其他模板处理器,用于不同的场景。
FMT处理器(格式化)
FMT处理器用来格式化字符串,类似String.format(),但语法更简洁:
// FMT处理器示例(格式化数字)
double price = 99.99;
int quantity = 5;
// 格式化价格,保留两位小数
String formatted = FMT."价格: %6.2f\{price}, 数量: %d\{quantity}";
System.out.println(formatted); // 输出: 价格: 99.99, 数量: 5
RAW处理器(原始模板)
RAW处理器返回原始的模板对象,不进行字符串转换,可以用来自定义处理逻辑:
// RAW处理器示例
String name = "鹏磊";
StringTemplate template = RAW."Hello, \{name}!";
// 可以获取模板的片段和值
List<String> fragments = template.fragments(); // 获取模板片段
List<Object> values = template.values(); // 获取嵌入的值
System.out.println("片段: " + fragments); // 输出: [Hello, , !]
System.out.println("值: " + values); // 输出: [鹏磊]
实际应用场景
字符串模板在实际开发中有很多应用场景,咱来看看几个常见的。
场景1:日志记录
以前写日志,得用String.format()或者+号拼接,代码又臭又长:
// 老写法,用String.format()
String name = "用户A";
int userId = 12345;
String action = "登录";
log.info(String.format("用户[%s](ID:%d)执行了操作:%s", name, userId, action));
// 新写法,用字符串模板
log.info(STR."用户[\{name}](ID:\{userId})执行了操作:\{action}");
场景2:SQL查询构建
构建SQL查询的时候,字符串模板特别有用,特别是动态查询:
// 构建动态SQL查询
String tableName = "users";
String column = "name";
String value = "鹏磊";
// 用字符串模板构建SQL(注意:实际项目中要用参数化查询防止SQL注入)
String sql = STR."""
SELECT * FROM \{tableName}
WHERE \{column} = '\{value}'
ORDER BY id DESC
""";
System.out.println(sql);
// 输出:
// SELECT * FROM users
// WHERE name = '鹏磊'
// ORDER BY id DESC
场景3:HTML生成
生成HTML的时候,字符串模板让代码更清晰:
// 生成HTML内容
String title = "Java教程";
String author = "鹏磊";
String content = "这是教程内容...";
String html = STR."""
<div class="article">
<h1>\{title}</h1>
<p class="author">作者: \{author}</p>
<div class="content">\{content}</div>
</div>
""";
System.out.println(html);
场景4:URL拼接
拼接URL的时候,字符串模板比+号拼接清晰多了:
// 拼接API URL
String baseUrl = "https://api.example.com";
String version = "v1";
String endpoint = "users";
int userId = 12345;
String url = STR."\{baseUrl}/api/\{version}/\{endpoint}/\{userId}";
System.out.println(url); // 输出: https://api.example.com/api/v1/users/12345
场景5:错误消息
生成错误消息的时候,字符串模板让消息更清晰:
// 生成错误消息
String fieldName = "email";
String reason = "格式不正确";
String errorMsg = STR."字段 '\{fieldName}' 验证失败: \{reason}";
System.out.println(errorMsg); // 输出: 字段 'email' 验证失败: 格式不正确
自定义模板处理器
除了内置的处理器,你还可以自定义模板处理器,实现自己的处理逻辑。
实现自定义处理器
自定义处理器需要实现StringTemplate.Processor接口:
// 自定义JSON处理器示例
import java.util.StringTemplate;
public class JSONProcessor implements StringTemplate.Processor<String, RuntimeException> {
@Override
public String process(StringTemplate template) throws RuntimeException {
// 获取模板片段和值
List<String> fragments = template.fragments();
List<Object> values = template.values();
// 构建JSON字符串
StringBuilder json = new StringBuilder("{");
for (int i = 0; i < fragments.size() - 1; i++) {
// 处理键值对(简化示例)
String key = fragments.get(i).trim().replaceAll("[:\\s]+", "");
Object value = values.get(i);
json.append("\"").append(key).append("\": ");
if (value instanceof String) {
json.append("\"").append(value).append("\"");
} else {
json.append(value);
}
if (i < fragments.size() - 2) {
json.append(", ");
}
}
json.append("}");
return json.toString();
}
}
// 使用自定义处理器
JSONProcessor JSON = new JSONProcessor();
String name = "鹏磊";
int age = 30;
String json = JSON."name: \{name}, age: \{age}";
System.out.println(json); // 输出: {"name": "鹏磊", "age": 30}
处理器的异常处理
自定义处理器可以抛出异常,用来做验证或错误处理:
// 带异常处理的自定义处理器
public class SafeSQLProcessor implements StringTemplate.Processor<String, SQLException> {
@Override
public String process(StringTemplate template) throws SQLException {
List<Object> values = template.values();
// 检查是否有SQL注入风险
for (Object value : values) {
if (value instanceof String) {
String str = (String) value;
// 简单的SQL注入检测(实际项目中要用更严格的验证)
if (str.contains(";") || str.contains("--") || str.contains("DROP")) {
throw new SQLException("检测到潜在的SQL注入风险: " + str);
}
}
}
// 安全处理模板
return STR.process(template);
}
}
性能考虑
字符串模板的性能怎么样?这是很多兄弟关心的问题。
编译时优化
字符串模板在编译时会被优化,性能接近StringBuilder:
// 字符串模板会被编译器优化
String name = "鹏磊";
int age = 30;
// 编译后,这个表达式会被优化成高效的字符串拼接
String result = STR."我叫\{name},今年\{age}岁";
与StringBuilder对比
对于简单的拼接,字符串模板的性能和StringBuilder差不多;对于复杂场景,字符串模板可能稍微慢一点,但差距不大:
// 性能测试示例
long start1 = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("数字: ").append(i).append("\n");
}
String result1 = sb.toString();
long end1 = System.nanoTime();
long start2 = System.nanoTime();
StringBuilder result2 = new StringBuilder();
for (int i = 0; i < 1000; i++) {
result2.append(STR."数字: \{i}\n");
}
long end2 = System.nanoTime();
// 实际测试中,性能差距很小,可以忽略
最佳实践
对于性能敏感的场景,建议:
- 简单拼接用字符串模板,代码更清晰
- 大量循环拼接用
StringBuilder,性能更好 - 格式化用
FMT处理器,比String.format()更简洁
预览特性说明
字符串模板在JDK 24中还是预览特性,需要启用预览功能才能用。
编译时启用预览
编译的时候需要加--enable-preview参数:
# 编译时启用预览特性
javac --enable-preview --release 24 Main.java
运行时启用预览
运行的时候也需要加--enable-preview参数:
# 运行时启用预览特性
java --enable-preview Main
Maven配置
如果用Maven,需要在pom.xml里配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>24</source>
<target>24</target>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
常见问题和注意事项
用字符串模板的时候,有几个地方需要注意。
转义字符
模板字符串里的\{需要转义成\\{,如果想输出字面量\{:
// 转义示例
String literal = STR."这是字面量: \\{不会替换}";
System.out.println(literal); // 输出: 这是字面量: \{不会替换}
null值处理
如果嵌入的值是null,STR处理器会把它转换成字符串"null":
// null值处理
String name = null;
String result = STR."名字: \{name}";
System.out.println(result); // 输出: 名字: null
嵌套模板
可以嵌套使用模板表达式,但要注意可读性:
// 嵌套模板示例
String outer = "外层";
String inner = "内层";
String nested = STR."\{outer} -> \{STR."\{inner}"}";
System.out.println(nested); // 输出: 外层 -> 内层
总结
字符串模板是JDK 24引入的一个很实用的特性,虽然还是预览版,但已经能解决不少字符串拼接的问题了。它让代码更简洁、更清晰,还能自动处理转义,提高安全性。
主要优势:
- 语法简洁:不用再写一堆
+号,代码更清晰 - 安全性高:自动转义特殊字符,防止注入攻击
- 功能强大:支持多行字符串、格式化、自定义处理器
- 性能不错:编译时优化,性能接近
StringBuilder
适用场景:
- 日志记录
- SQL查询构建(注意安全性)
- HTML生成
- URL拼接
- 错误消息生成
虽然还是预览特性,但已经能看到Java在朝着更现代化的方向发展了。兄弟们可以试试,特别是那些经常需要字符串拼接的场景,用起来确实方便。等正式发布后,肯定会成为Java开发的标准做法。