这篇文章详细介绍了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 在内存受限环境中高并发应用程序的领先地位。
字符串插值在多数编程语言中已得到支持。这一特性是指一个结合了表达式和文本字面量的字符串字面量。
例如,在 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}" 是无法编译的。这是字符串模板与普通字符串的不同之处。
标准库提供了三个模板处理器:
你还可以通过实现 StringTemplate.Processor 接口来创建自定义的处理器。
更多详情,请参阅:JEP 430 。
Java 21 在集合 API 中引入了一个创新性的扩展,这样的扩展在许多先前的版本中尚未出现过。
在过去的 Java 版本中,集合缺乏代表有序元素序列的类型。然而,Java 21 通过引入了 SequencedCollection、SequencedSet 和 SequencedMap 接口,弥补了这一不足。这些接口定义了在集合开头或结尾处添加、修改或删除元素的方法,以及以逆序方式遍历集合的功能。
下面展示了 SequencedCollection 接口:
interface SequencedCollection<E> extends Collection<E> {
SequencedCollection<E> reversed();
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
reversed() 方法将返回一个集合的逆序视图,并且对原始集合所做的任何更改都会反映在这个逆序视图上。
SequencedSet 则是一个继承了 SequencedCollection 的集合类型,以下是它的接口定义:
interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
SequencedSet<E> reversed();
}
此外,SequencedMap 作为一种条目有序的映射,其接口如下所示:
interface SequencedMap<K,V> extends Map<K,V> {
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
SequencedSet<Entry<K,V>> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
Entry<K,V> firstEntry();
Entry<K,V> lastEntry();
Entry<K,V> pollFirstEntry();
Entry<K,V> pollLastEntry();
}
这些新接口已经完美融合到现有的 Collection API 类层次结构中。
值得一提的是,一些在 Collection API 中广泛使用的实现,如 ArrayList、LinkedList、LinkedHashMap 和 LinkedHashSet,现在已经支持顺序集合特性。
有关更多详细信息,请参阅 JEP 431。
Java 语言引入了一项新特性,即允许使用下划线 _ 来表示未命名模式或变量。这一特性旨在用 _ 标识无效或不需要使用的模式或变量,编译器将确保这样的变量真实地未被使用,因为它们是无名的。
在 Java 的模式匹配过程中,_ 可作为未命名模式使用,如 instanceof Point(_, int y),或作为未命名模式变量使用,例如 instanceof Point(int _, int y) 或 case Box(GreenBall _)。
下划线 _ 还可以表示不可读写的未命名变量。由于它们没有名称,在同一作用域中可以多次使用此类未命名变量。
未命名变量可以在以下场合使用:
以下是 JEP 443 中的示例代码:
// 增强 for 循环
int acc = 0;
for (Order _ : orders) {
if (acc < LIMIT) {
... acc++ ...
}
}
// 块语句
Queue<Integer> q = ... // x1, y1, z1, x2, y2, z2, ...
while (q.size() >= 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。
这一新特性旨在让 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。
以下功能已从预览(或孵化模块)阶段转型为正式标准功能:
以下功能依然处于预览(或孵化模块)状态:
JDK 的各种补充:
所有新的 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 版本中的哪个功能?请在评论区谈谈你的看法。
参考链接