Administrator
发布于 2023-08-30 / 1438 阅读
27

字符串模板预览特性体验

一个被语法糖耽误的拼接需求

上周我要拼一段带变量的 SQL 调试日志,里面混入三个变量。老写法要么是加号拼接,要么是 String.format,前者冗长后者位置参数容易错位。听同事说 JDK 21 的预览特性 String Templates 把这件事做漂亮了,我拉了 early-access 构建试了试,结论是:方向真不错,但在生产落地前还有几处要想清楚。

三种写法摆在一起

同样的需求,三种写法对比一下,差异立刻显现:

String user = "zixin";
int age = 29;
String city = "Shanghai";

// 1. 加号拼接:变量一多就满屏加号,括号容易漏
String s1 = "用户 " + user + " 年龄 " + age + " 城市 " + city;

// 2. String.format:位置参数,顺序错就乱套,还丢类型信息
String s2 = String.format("用户 %s 年龄 %d 城市 %s", user, age, city);

// 3. String Templates(STR 处理器)
String s3 = STR."用户 \{user} 年龄 \{age} 城市 \{city}";

第三种最直观。\{ } 里直接塞表达式,编译期由 STR 处理器展开。我跑了 100 万次拼接做基准:三者耗时分别是 89ms、142ms、91ms。STR 和加号拼接几乎打平,比 format 快不少,因为 format 每次都要解析占位符字符串。性能不是重点,重点是可读性。

可读性才是真正的收益

真实业务里经常是十几个字段拼一条消息,加号写法要数清楚哪段接哪段,format 写法要靠 %s 和下方的参数列表一一对应,少传一个就 IndexOutOfBoundsException,多传一个没人发现。STR 把变量直接嵌在它该在的位置,眼到手到。我把团队一段 40 行的订单日志拼装改成 STR,缩到 12 行,新人看一眼就懂。

维度加号拼接String.formatString Templates
可读性差(变量散落)中(位置易错)好(就近嵌入)
类型安全编译期运行期才报错编译期
百万次耗时89ms142ms91ms
占位错位风险

预览特性怎么跑起来

它还是预览,得显式开启:

javac --release 21 --enable-preview Demo.java
java  --enable-preview Demo

用 Maven 的话,compiler 插件要加 enable-preview:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <release>21</release>
    <compilerArgs>--enable-preview</compilerArgs>
  </configuration>
</plugin>

注意 surefire 跑测试也要加 --enable-preview,否则测试编译不过,这个坑我踩了半小时。另外构建产物的 class 文件带预览标记,运行时必须用同版本 JDK 加 --enable-preview,否则报 UnsupportedClassVersionError。

它能做的远不止拼接

STR 只是内置处理器之一。FMT 可以做本地化格式化,比如按区域显示数字和日期:

String s = FMT."金额 \{amount,number,currency} 时间 \{now,time,short}";

更有用的是 RAW 处理器,它把模板拆成片段和值两部分,方便做 SQL 预编译或 HTML 转义:

StringTemplate st = RAW."SELECT * FROM t WHERE name = '\{name}'";
// st.fragments() 拿到 SQL 骨架,st.values() 拿到参数,交给 PreparedStatement

把骨架和值分开,注入就无从谈起。这个思路比手动拼字符串安全多了,我们后来在内部工具里用 RAW 统一做了一层防注入。还有一点巧妙:你可以自己写 TemplateProcessor,比如把模板渲染成 JSON 或 Markdown,扩展性比 format 强太多。

什么时候我不建议用

一是生产环境,预览特性的语法和处理器 API 在转正前可能变动,升级 JDK 时要重测;二是超高频、极致性能的循环里,虽然它和加号差不多快,但多一层处理器调用,能省则省。日常业务代码、日志、SQL 拼装,它是把好刀。

和文本块(Text Blocks)配合更香

JDK 15 转正的多行文本块解决的是"换行和缩进",String Templates 解决的是"插值"。两者可以叠加,写一段带变量的 JSON 或 HTML 就舒服了:

String body = STR."""
{
  "user": "\{user}",
  "age":  \{age},
  "city": "\{city}"
}""";

以前这种 JSON 要么拼字符串要么引 Jackson,现在模板直接出。我们内部有个生成测试数据的小工具,原来 60 行 StringBuilder,改成 STR 加文本块后 20 行,而且不会漏掉引号转义。

一个我踩过的小坑

\{ } 里的表达式如果是方法调用,要注意它会每次求值。我一开始在模板里写了 STR."共 \{list.size()} 条",循环里反复拼接,结果 list.size() 被调了好几次。虽然这里没副作用不影响结果,但如果表达式有副作用就会重复执行。约定上模板里只放纯读取表达式,副作用留在外面算好再传进来。

小结

字符串模板目前还是预览特性,生产别急着上,局部工具脚本可以试。但方向我很喜欢:把"模板加处理器"解耦,拼接只是最朴素的一种。等它正式转正,日志、SQL、JSON 拼装都能更干净,也少很多注入隐患。我已经在个人项目里全面用上了,就等它在主项目里转正。如果你也在用 JDK 21,建议先在一个非核心脚本里试水,感受一下它和加号、format 在可读性和可维护性上的差距。

参考