2. Java中的字符串组合

在编程中,我们经常需要拼接字符串。Java 提供了多种方法,但每种都有明显的短板。

2.1. 字符串拼接

最基础的方式是使用 + 号拼接字符串字面量和表达式:

String composeUsingPlus(String feelsLike, String temperature, String unit){
    return "Today's weather is " + feelsLike + 
      ", with a temperature of " + temperature + " degrees " + unit;
}

虽然功能实现了,但代码可读性差(全是加号),维护起来简直是噩梦。

2.2. StringBuilder/StringBuffer

使用 Java 内置的 StringBuilderStringBuffer 类,通过 append() 方法拼接:

String composeUsingStringBuilder(String feelsLike, String temperature, String unit) {
    return new StringBuilder()
      .append("Today's weather is ")
      .append(feelsLike)
      .append(", with a temperature of ")
      .append(temperature)
      .append(" degrees ")
      .append(unit)
      .toString();
}

虽然内存效率高,但遵循 Builder 模式的写法太啰嗦,代码量爆炸。

2.3. 格式化字符串

通过 String.format()formatted() 分离静态模板和动态参数:

String composeUsingFormatters(String feelsLike, String temperature, String unit) {
    return String.format("Today's weather is %s, with a temperature of %s degrees %s", 
      feelsLike, temperature, unit);
}

模板固定了,但参数顺序和数量必须严格匹配,稍有不慎就翻车。

2.4. MessageFormat 类

java.text.MessageFormat 支持带占位符的国际化文本拼接:

String composeUsingMessageFormatter(String feelsLike, String temperature, String unit) {
    return MessageFormat.format("Today''s weather is {0}, with a temperature of {1} degrees {2}",
      feelsLike, temperature, unit);
}

同样存在参数顺序问题,而且语法和常规字符串写法差异太大,用起来别扭。

3. 字符串模板入门

传统方法各有硬伤,字符串模板(String Templates)作为 Java 21 的预览特性(JEP 430)给出了解决方案。

3.1. 设计目标

引入字符串模板的核心目标:

  • 简化运行时拼接字符串的表达方式
  • 提升可读性,告别 StringBuilder 的冗长代码
  • 在安全性和便利性之间找平衡,避免其他语言中字符串插值的安全漏洞
  • 允许第三方库自定义字符串格式化语法

3.2. 模板表达式

核心概念是模板表达式——一种新型可编程表达式:

  • ✅ 不仅能做字符串插值
  • ✅ 保证组合过程的安全与高效
  • ✅ 输出结果不限于字符串(可转为任意对象)

模板表达式由三部分组成:

  1. 处理器(Processor)
  2. 包含嵌入表达式的模板
  3. 点号(.)分隔符

4. 模板处理器

处理器负责在运行时计算嵌入表达式,将其与字符串字面量合并生成最终结果。 Java 提供内置处理器,也支持自定义处理器。

⚠️ 这是 Java 21 的预览特性,需启用 --enable-preview

4.1. STR 处理器

STR 是最常用的内置处理器,通过将嵌入表达式替换为字符串化值实现插值

String interpolationUsingSTRProcessor(String feelsLike, String temperature, String unit) {
    return STR
      . "Today's weather is \{ feelsLike }, with a temperature of \{ temperature } degrees \{ unit }" ;
}

STR 是自动导入的公共静态常量字段。

支持多行文本块(用 """ 包裹):

String interpolationOfJSONBlock(String feelsLike, String temperature, String unit) {
    return STR
      . """
      {
        "feelsLike": "\{ feelsLike }",
        "temperature": "\{ temperature }",
        "unit": "\{ unit }"
      }
      """ ;
}

还能直接嵌入运行时计算的表达式:

String interpolationWithExpressions() {
    return STR
      . "Today's weather is \{ getFeelsLike() }, with a temperature of \{ getTemperature() } degrees \{ getUnit() }";
}

4.2. FMT 处理器

FMT 处理器支持格式化指令(类似 java.util.Formatter):

String interpolationOfJSONBlockWithFMT(String feelsLike, float temperature, String unit) {
    return FMT
      . """
      {
        "feelsLike": "%1s\{ feelsLike }",
        "temperature": "%2.2f\{ temperature }",
        "unit": "%1s\{ unit }"
      }
      """ ;
}

通过 %s%f 等指令控制输出格式,比如保留两位小数。

4.3. 模板表达式求值过程

以这段代码为例:

STR
  . "Today's weather is \{ feelsLike }, with a temperature of \{ temperature } degrees \{ unit }" ;

实际执行分三步:

  1. 获取处理器实例(这里是 STR
  2. 创建未处理的模板对象(通过 RAW 处理器):
    StringTemplate str = RAW
      . "Today's weather is \{ getFeelsLike() }, with a temperature of \{ getTemperature() } degrees \{ getUnit() }" ;
    
  3. 调用处理器的 process() 方法:
    return STR.process(str);
    

5. 字符串插值与模板的关系

虽然模板表达式看起来像字符串插值,但安全性是其核心优势

  • ❌ 禁止直接将带嵌入表达式的字符串字面量转为输出字符串
  • ✅ 处理器必须验证插值的安全性和正确性
  • ❌ 缺少处理器会触发编译错误
  • ❌ 处理器插值失败会抛出异常

Java 根据嵌入表达式是否存在来区分:

  • "text"StringLiteral(无嵌入表达式)
  • "text \{expr}"StringTemplate(有嵌入表达式)
  • """text"""TextBlock
  • """text \{expr}"""TextBlockTemplate

关键区别: 模板是 java.lang.StringTemplate 接口类型,不是 java.lang.String

6. 总结

我们对比了传统字符串组合方式的缺陷,深入解析了 Java 21 中字符串模板的设计哲学。通过 STR/FMT 处理器和模板表达式机制,Java 在保持类型安全的前提下,提供了简洁高效的字符串拼接方案,比直接字符串插值更可靠。

示例代码已上传至 GitHub


原始标题:String Templates in Java | Baeldung