这篇文章详细介绍了Java 21 的新特性和改进。Java 21是新的长期支持(LTS)版本,其中包括了15个Java增强提案(JEPs)。其中最重要的特性之一是虚拟线程的最终化,这些线程的创建和调度成本较低,使并发应用程序的编写变得更容易。文章还介绍了一些新的预览特性,如字符串模板(用于字符串插值)、序列化集合(增强了 Java 的集合 API)、未命名模式和变量、未命名类和实例 main 方法等。此外,还讨论了从预览状态转变为标准特性的一些功能,如记录模式、switch 的模式匹配和虚拟线程等。文章还提到了性能和安全等方面的改进。
现在 Java 21 已经功能完备,并进入了 Rampdown 第二阶段,本文将深入介绍这个新版本的主要功能。
Java 21 是新推出的长期支持版本(Long Term Support ,LTS),将会提供两年的技术支持。其中包含的 JEP(Java 增强提案)数量不下于 15 个,这个版本功能非常丰富。最引人注目的是虚拟线程已经完全实现了!作为轻量级线程,虚拟线程的创建和调度成本低,让编写并发应用程序变得更简单。尽管整个生态系统对虚拟线程的支持还需时日,虚拟线程的引入无疑将确保 Java 在内存受限环境中高并发应用程序的领先地位。
JEP 430 – 字符串模板(预览)
字符串插值在多数编程语言中已得到支持。这一特性是指一个结合了表达式和文本字面量的字符串字面量。
例如,在 Kotlin 中,表达式 "xplusx plus xplusy equals x+y"包含了{x + y}" 包含了 x+y"包含了x、y和y 和 y和{x + y},这些表达式会被转换为相应的文本值。这一过程称为字符串内的插值,它基于变量并允许变量间的运算,例如执行加法运算。
不过,因为在构造最终字符串的过程中无法进行验证或清洗,字符串插值作为全局特性会带来一些风险。例如,它可能增加了受到 SQL 或 JavaScript 注入攻击的风险。
在 Java 中,字符串模板是通过模板处理器来实现,该处理器能够在插值过程中提供验证和清理操作。
模板处理器接受一个模板,并将其插值到特定类型的对象,如String,或者一个 PreparedStatement,或者一个 JSONObject 等。支持多个处理器,每个处理器都可执行验证步骤(如果需要)。
下面的示例展示了如何使用没有特定验证的字符串模板 STR,作为字符串连接的替代方法:
String firstName = "Loïc";
String lastName = "Mathieu";
String helloWorld = STR."Hello {firstName} {lastName}";
在 Java 中,表达式由 {expression} 定义,并通过名为 FMT 的模板处理器调用,这是先前通过静态导入导入的 StringTemplate 接口的常量。
关于表达式格式的选择有很多不同的观点。许多库选择使用 $、# 或 {} 作为表达式分隔符,因此选择了一个字符串模板之外无效的格式:String s = "Hello {firstName} {lastName}" 是无法编译的。这是字符串模板与普通字符串的不同之处。
标准库提供了三个模板处理器:
- RAW:不对字符字符串进行插值的处理器,允许低级操作。
- STR:通过简单连接将字符字符串插入到另一个字符字符串的处理器。
- FMT:通过 Formatter 等手段,允许格式化表达式的处理器,例如 FMT."%05d{x} + %05d{y} = %05d{x + y}";
你还可以通过实现 StringTemplate.Processor 接口来创建自定义的处理器。
更多详情,请参阅:JEP 430 。
JEP 431 – 顺序集合
Java 21 在集合 API 中引入了一个创新性的扩展,这样的扩展在许多先前的版本中尚未出现过。
在过去的 Java 版本中,集合缺乏代表有序元素序列的类型。然而,Java 21 通过引入了 SequencedCollection、SequencedSet 和 SequencedMap 接口,弥补了这一不足。这些接口定义了在集合开头或结尾处添加、修改或删除元素的方法,以及以逆序方式遍历集合的功能。
下面展示了 SequencedCollection 接口:
interface SequencedCollection extends Collection {
SequencedCollection reversed();
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
reversed() 方法将返回一个集合的逆序视图,并且对原始集合所做的任何更改都会反映在这个逆序视图上。
SequencedSet 则是一个继承了 SequencedCollection 的集合类型,以下是它的接口定义:
interface SequencedSet extends Set, SequencedCollection {
SequencedSet reversed();
}
此外,SequencedMap 作为一种条目有序的映射,其接口如下所示:
interface SequencedMap extends Map {
SequencedMap reversed();
SequencedSet sequencedKeySet();
SequencedCollection sequencedValues();
SequencedSet sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
Entry firstEntry();
Entry lastEntry();
Entry pollFirstEntry();
Entry pollLastEntry();
}
这些新接口已经完美融合到现有的 Collection API 类层次结构中。
值得一提的是,一些在 Collection API 中广泛使用的实现,如 ArrayList、LinkedList、LinkedHashMap 和 LinkedHashSet,现在已经支持顺序集合特性。
有关更多详细信息,请参阅 JEP 431。
JEP 443 – 未命名模式与变量预览
Java 语言引入了一项新特性,即允许使用下划线 _ 来表示未命名模式或变量。这一特性旨在用 _ 标识无效或不需要使用的模式或变量,编译器将确保这样的变量真实地未被使用,因为它们是无名的。
在 Java 的模式匹配过程中,_ 可作为未命名模式使用,如 instanceof Point(_, int y),或作为未命名模式变量使用,例如 instanceof Point(int _, int y) 或 case Box(GreenBall _)。
下划线 _ 还可以表示不可读写的未命名变量。由于它们没有名称,在同一作用域中可以多次使用此类未命名变量。
未命名变量可以在以下场合使用:
- 作为语句内部的局部变量;
- 用于 try-with-resource 结构的资源;
- 在 for 循环(包括基本和增强)的头部;
- 作为 catch 块的异常参数;
- 作为 lambda 表达式的参数。
以下是 JEP 443 中的示例代码:
// 增强 for 循环
int acc = 0;
for (Order _ : orders) {
if (acc = 3) {
var x = q.remove();
var y = q.remove();
var _ = q.remove();
... new Point(x, y) ...
}
// catch 块
String s = ...
try {
int i = Integer.parseInt(s);
... i ...
} catch (NumberFormatException _) {
System.out.println("Bad number: " + s);
}
// try-with-resources
try (var _ = ScopedContext.acquire()) {
... no use of acquired resource ...
}
// lambda 参数
stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA"))
虽然这个特性在表面上看似微小,但实际上它备受期待,能够通过清晰标识不应使用的变量,极大地提高代码的可读性,并避免潜在的错误。不过,目前还不能将 _ 用作重载方法的参数。
更多详细信息,请参阅 JEP 443。
JEP 445: 未命名类与实例 main 方法(预览版)
这一新特性旨在让 Java 在编写简单场景如基础 main 方法时变得更加容易学习和掌握。
以 Java 中经典的 "Hello World" 示例为例:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
要输出 "Hello World" 到控制台,你需理解有关类、方法、可见性以及 static 修饰符的原则,还要掌握 Java main 方法的具体签名,即作为程序入口点的执行方法。
第一项更改:允许非 static(实例方法)、非公共的无参数 main 方法:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
第二项更改:引入未命名的类:
void main() {
System.out.println("Hello, World!");
}
未命名类位于 .class 文件中,没有类声明,不能被其他类引用,但可包含方法和字段。它被归属于一个未命名的包。
这两项新特性主要针对学习 Java 的开发人员。通过减少编写 Java 程序入口点所需的额外步骤,大大便利了中小型 Java 程序的开发。
此外,关于 System.out.println()(以及控制台读取)和可能的简化也在讨论之列。未来的 Java 版本可能会在这方面进一步的简化。
更多详细信息,请参阅 JEP 445。
新增的标准功能
以下功能已从预览(或孵化模块)阶段转型为正式标准功能:
- JEP 440 – 记录模式:通过记录模式优化 Java 的模式匹配,能将记录细分为各个属性。
- JEP 441 – switch 模式匹配:允许基于变量的类型(包括枚举、记录和数组)执行 switch 操作,并从中提取相应类型的局部变量。
- JEP 444 – 虚拟线程:也可称为绿色线程或轻量级线程,它们的创建和调度成本较低,简化了并发应用程序的编写。
仍在预览阶段的功能
以下功能依然处于预览(或孵化模块)状态:
- JEP 442 – 外部函数和内存 API(第三次预览):该功能已根据预览反馈进行改进,现通过新的 Arena API 来管理本地内存段。
- JEP-448 – 向量 API:该功能正处于第六次孵化,新版本中包括了错误的修正以及性能的提升。
- JEP 446 – 作用域值(预览):该功能允许在线程内部及线程之间共享不可变数据,现处于预览阶段。
- JEP 453 – 结构化并发(预览):这是一种全新的 API,它通过将多个并发任务整合为单一处理单元来简化多线程代码的编写,目前仍处于预览阶段。
杂项
JDK 的各种补充:
- Character.isEmoji()、Character.isEmojiPresentation()、Character.isEmojiModifier()、Character.isEmojiModifierBase()、Character.isEmojiComponent()、Character.isExtendedPictographic()。
- Math.clamp() 和 StrictMath.clamp():将一个值限制在最小值和最大值之间。
- StringBuilder.repeat():将字符或字符串拼接特定次数。
- HttpClient 现在实现了 AutoCloseable,因此可以在 try-with-resources 块中更轻松地使用。
- Locale.availableLocales():返回已安装的区域设置列表。
- Collections.shuffle(List, RandomGenerator):使用 RandomGenerator 对列表的元素进行随机排列。
- String.splitWithDelimiters() 和 Pattern.splitWithDelimiters():使用分隔符来分割字符串。
所有新的 JDK 21 API 可以在 Java 版本年鉴 - Java 21 中的新 API 中找到。
内部变化、性能和安全性
ZGC 垃圾收集器现已升级为分代收集方式,以便根据对象的年龄将堆划分为多个区域。要激活这项功能,可使用命令行选项 -XX:+ZGenerational。ZGC 的设计初衷是支持极大的堆(数太字节)并实现非常低的暂停时间(毫秒级)。采用分代堆策略使其能在更低资源消耗下支持不同工作负载。详细信息可参考 JEP 439。
在安全领域,Java 已经引入了密钥封装机制(KEM),这是一种利用公钥加密来保护对称密钥的方法。详细信息可参考 JEP 452。
Windows 将在未来的版本中废弃 32 位端口的 x86 CPU。Windows 10 将成为支持 32 位架构的最后一个版本,其到期日期定于 2025 年 10 月。简化 Open JDK 的构建并降低维护成本的方式是弃用并最终移除 Windows 32 位端口。详细信息可参考 JEP 449。
目前,Java 动态代理加载已被标记为废弃。如果使用该功能,JVM 日志将显示警告。Java 代理在程序启动时的加载仍然受支持,但程序启动后的动态加载已被废弃。此举旨在增强 JVM 的完整性,因为代理可修改应用程序代码,运行时加载它可能带来安全风险。详细信息可参考 JEP 451。
在性能方面,Per Minborg 改进了基本数据类型之间的转换(例如,long 转 int),方法是用 VarHandle 替换现有的二进制计算。由于这些转换在 Java 序列化中广泛应用,因此性能提升近 5%。其他 JDK API 和许多库也将从这些转换中受益。详细信息请参见 Per Minborg 的文章:Java 21:性能改进揭秘。
结论
考虑到 Java 21 中所包括的丰富功能,我们有理由期待 Java 22 将成为一个稳定版本。我期待 Java 22 中的某些功能将走出预览状态,特别是 外部函数与内存API(Foreign Function & Memory API),还有作用域值(Scoped Values)和 结构化并发(Structured Concurrency),后两者通过简化并发应用程序的编写来补充虚拟线程(Virtual Threads)。
要查找 Java 21 中的所有更改,请参阅发行说明。
你现在工作中使用的是哪个 Java 版本?你最期待 Java 21 版本中的哪个功能?请在评论区谈谈你的看法。
参考链接