Java 21 已于 2023 年 9 月 19 日发布,是 Oracle 标准 Java 实现的下一个长期支持(LTS)版本。Java 21 具有以下 15 项新特性。
这个特性虽然还在预览阶段,还不属于正式发布,但是若能最终进入发布,能够大大减轻了Java编程时复杂字符串的编辑处理工作量。
开发人员经常使用字面文本和表达式组合来组成字符串。Java 提供了多种字符串组合机制,但遗憾的是,所有机制都有缺点。
String s = x + " plus " + y + " equals " + (x + y);
String s = new StringBuilder()
.append(x)
.append(" plus ")
.append(y)
.append(" equals ")
.append(x + y)
.toString();
String s = String.format("%2$d plus %1$d equals %3$d", x, y, x + y);
String t = "%2$d plus %1$d equals %3$d".formatted(x, y, x + y);
MessageFormat mf = new MessageFormat("{0} plus {1} equals {2}");
String s = mf.format(x, y, x + y);
模板表达式(Template expressions)是 Java 编程语言中的一种新型表达式。模板表达式不仅可以执行字符串插值,还可以编程,从而帮助开发人员安全高效地组成字符串。此外,模板表达式并不局限于组成字符串——它们可以根据特定领域的规则将结构化文本转化为任何类型的对象。
STR 是 Java 平台定义的一种模板处理器。它通过用表达式的(stringified)值替换模板中的每个嵌入表达式来执行字符串插值。使用 STR 的模板表达式的求值结果是一个字符串。
STR 是一个公共静态 final 字段,会自动导入到每个 Java 源文件中。
// Embedded expressions can be strings
String firstName = "Bill";
String lastName = "Duck";
String fullName = STR."\{firstName} \{lastName}";
程序结果:Bill Duck
String sortName = STR."\{lastName}, \{firstName}";
程序结果:Duck, Bill
// Embedded expressions can perform arithmetic
int x = 10, y = 20;
String s = STR."\{x} + \{y} = \{x + y}";
程序结果:"10 + 20 = 30"
// Embedded expressions can invoke methods and access fields
String s = STR."You have a \{getOfferType()} waiting for you!";
程序结果:"You have a gift waiting for you!"
String t = STR."Access at \{req.date} \{req.time} from \{req.ipAddress}";
程序结果:Access at 2022-03-25 15:34 from 8.8.8.8
String filePath = "tmp.dat";
File file = new File(filePath);
String old = "The file " + filePath + " " + (file.exists() ? "does" : "does not") + " exist";
String msg = STR."The file \{filePath} \{file.exists() ? "does" : "does not"} exist";
程序结果:The file tmp.dat does exist
或者是:The file tmp.dat does not exist
String time = STR."The time is \{
// The java.time.format package is very useful
DateTimeFormatter
.ofPattern("HH:mm:ss")
.format(LocalTime.now())
} right now";
程序结果:The time is 12:34:56 right now
// Embedded expressions can be postfix increment expressions
int index = 0;
String data = STR."\{index++}, \{index++}, \{index++}, \{index++}";
程序结果:"0, 1, 2, 3"
// Embedded expression is a (nested) template expression
String[] fruit = { "apples", "oranges", "peaches" };
String s = STR."\{fruit[0]}, \{STR."\{fruit[1]}, \{fruit[2]}"}";
程序结果:apples, oranges, peaches
上面的代码fruit[1]和fruit[2]所在的模板被嵌入,是不是读起来非常困难,可以优化成这样:
String s = STR."\{fruit[0]}, \{
STR."\{fruit[1]}, \{fruit[2]}"
}";
也可以这样:
String tmp = STR."\{fruit[1]}, \{fruit[2]}";
String s = STR."\{fruit[0]}, \{tmp}";
模板表达式的模板可以跨越多行源代码,使用的语法与文本块类似。(我们在上文看到了一个跨多行的嵌入式表达式,但包含该嵌入式表达式的模板在逻辑上只有一行)。
以下是表示 HTML 文本、JSON 文本和区域表的模板表达式的示例,它们都跨多行:
String title = "My Web Page";
String text = "Hello, world";
String html = STR."""
\{title}
\{text}
""";
程序结果:
<html>
<head>
<title>My Web Page</title>
</head>
<body>
<p>Hello, world</p>
</body>
</html>
String name = "Joan Smith";
String phone = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = STR."""
{
"name": "\{name}",
"phone": "\{phone}",
"address": "\{address}"
}
""";
程序结果:
{
"name": "Joan Smith",
"phone": "555-123-4567",
"address": "1 Maple Drive, Anytown"
}
record Rectangle(String name, double width, double height) {
double area() {
return width * height;
}
}
Rectangle[] zone = new Rectangle[] {
new Rectangle("Alfa", 17.8, 31.4),
new Rectangle("Bravo", 9.6, 12.4),
new Rectangle("Charlie", 7.1, 11.23),
};
String table = STR."""
Description Width Height Area
\{zone[0].name} \{zone[0].width} \{zone[0].height} \{zone[0].area()}
\{zone[1].name} \{zone[1].width} \{zone[1].height} \{zone[1].area()}
\{zone[2].name} \{zone[2].width} \{zone[2].height} \{zone[2].area()}
Total \{zone[0].area() + zone[1].area() + zone[2].area()}
""";
程序结果:
Description Width Height Area
Alfa 17.8 31.4 558.92
Bravo 9.6 12.4 119.03999999999999
Charlie 7.1 11.23 79.733
Total 757.693
FMT 是 Java 平台定义的另一种模板处理器。FMT 与 STR 类似,它执行插值,但也解释嵌入表达式左侧出现的格式规范。
FMT 需要手动执行导入
import static java.util.FormatProcessor.FMT;
格式说明符与 java.util.Formatter 中定义的格式说明符相同。例如:
record Rectangle(String name, double width, double height) {
double area() {
return width * height;
}
}
Rectangle[] zone = new Rectangle[] {
new Rectangle("Alfa", 17.8, 31.4),
new Rectangle("Bravo", 9.6, 12.4),
new Rectangle("Charlie", 7.1, 11.23),
};
String table = FMT."""
Description Width Height Area
%-12s\{zone[0].name} %7.2f\{zone[0].width} %7.2f\{zone[0].height} %7.2f\{zone[0].area()}
%-12s\{zone[1].name} %7.2f\{zone[1].width} %7.2f\{zone[1].height} %7.2f\{zone[1].area()}
%-12s\{zone[2].name} %7.2f\{zone[2].width} %7.2f\{zone[2].height} %7.2f\{zone[2].area()}
\{" ".repeat(28)} Total %7.2f\{zone[0].area() + zone[1].area() + zone[2].area()}
""";
程序输出:
Description Width Height Area
Alfa 17.80 31.40 558.92
Bravo 9.60 12.40 119.04
Charlie 7.10 11.23 79.73
Total 757.69
模板处理器在运行时执行,而不是在编译时执行,因此无法对模板进行编译时处理。它们也无法获得源代码中出现在模板中的确切字符;只能获得嵌入式表达式的值,而不能获得嵌入式表达式本身。
支持用户定义的模板处理器。前面我们看到了模板处理器 STR 和 FMT,它们让人觉得模板处理器是一个通过字段访问的对象。其实,模板处理器是一个对象,它是方法接口 StringTemplate.Processor 的实例。开发人员可以轻松创建模板处理器,用于模板表达式。
!!!注意不要把字符串模板功能用于SQL拼接,稍不注意就引入了SQL注入风险
!!!另外要特别提醒的是,这个特性目前还在Preview状态,还没有正式发布,请不要用于生产环境。只是感叹Java还在不停的改进,学习其他语言优秀的地方,是广大开发者的福音
JDK21新特性之虚拟线程
后续整理代际ZGC439: Generational ZGC