1. 引言
本教程将深入探讨最新发布的 Java 22(现已正式可用),全面解析其核心更新与改进。
2. Java 语言更新
2.1. 未命名变量与模式 – JEP 456
开发中常遇到定义后未被使用的临时变量或模式变量,这通常源于语言限制,删除它们可能导致副作用或直接报错。典型场景包括异常处理、switch 模式匹配和 Lambda 表达式:
try {
int number = someNumber / 0;
} catch (ArithmeticException exception) {
System.err.println("Division by zero");
}
switch (obj) {
case Integer i -> System.out.println("Is an integer");
case Float f -> System.out.println("Is a float");
case String s -> System.out.println("Is a String");
default -> System.out.println("Default");
}
try (Connection connection = DriverManager.getConnection(url, user, pwd)) {
LOGGER.info(STR."""
DB Connection successful
URL = \{url}
usr = \{user}
pwd = \{pwd}""");
} catch (SQLException e) {}
未命名变量(_
)完美解决了这类问题,清晰表达变量意图。它们不能被传递、使用或赋值。 重写后的代码如下:
try {
int number = someNumber / 0;
} catch (ArithmeticException _) {
System.err.println("Division by zero");
}
switch (obj) {
case Integer _ -> System.out.println("Is an integer");
case Float _ -> System.out.println("Is a float");
case String _ -> System.out.println("Is a String");
default -> System.out.println("Default");
}
try (Connection _ = DriverManager.getConnection(url, user, pwd)) {
LOGGER.info(STR."""
DB Connection successful
URL = \{url}
usr = \{user}
pwd = \{pwd}""");
} catch (SQLException e) {
LOGGER.warning("Exception");
}
2.2. super() 前的语句 – JEP 447
长期以来,Java 不允许在子类构造函数的 super()
调用前放置任何语句。假设存在继承自 Shape
的 Square
和 Circle
类,子类构造函数的首行必须是 super()
:
public class Square extends Shape {
int sides;
int length;
Square(int sides, int length) {
super(sides, length);
// 其他代码
}
}
当需要在调用 super()
前进行参数验证时,这种限制非常不便。Java 22 解决了这个问题:
public class Square extends Shape {
int sides;
int length;
Square(int sides, int length) {
if (sides != 4 && length <= 0) {
throw new IllegalArgumentException("Cannot form Square");
}
super(sides, length);
}
}
⚠️ 注意:super()
前的语句不能访问实例变量或执行实例方法,仅适用于参数验证和值转换。
此为预览特性。
3. 字符串模板 – JEP 459
Java 22 引入字符串模板的第二次预览。该特性允许嵌入字面文本、表达式和模板处理器,生成定制化结果,是传统字符串拼接更安全高效的替代方案。
本次预览对 API 进行了微调:模板表达式的类型现在由模板处理器的 process()
方法返回类型决定。
4. 隐式声明类与实例 main 方法 – JEP 463
Java 终于支持无需显式定义类或标准 main 方法模板的程序编写方式。 传统写法:
class MyClass {
public static void main(String[] args) {
}
}
现在可直接创建文件并编写 main()
方法:
void main() {
System.out.println("This is an implicitly declared class without any constructs");
int x = 165;
int y = 100;
System.out.println(y + x);
}
可直接使用文件名编译。隐式类位于无名包内的无名模块中。
5. 库更新
5.1. 外部函数与内存 API – JEP 454
经过多轮孵化,Java 22 最终确定 外部函数与内存 API。**该 API 允许调用 JVM 生态外的函数(外部函数)并访问 JVM 外部内存。
相比 JNI,它提供了更高效、更安全的方式访问其他运行时和语言的库。该 API 覆盖所有 JVM 支持平台,提供丰富的操作接口,支持堆内存和瞬时内存等多种内存类型下的结构化/非结构化数据处理。
使用新 API 调用 C 的 strlen()
函数计算字符串长度:
public long getLengthUsingNativeMethid(String string) throws Throwable {
SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();
MethodHandle strlen =
Linker.nativeLinker()
.downcallHandle(
stdlib.find("strlen").orElseThrow(),
of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
try (Arena offHeap = Arena.ofConfined()) {
MemorySegment str = offHeap.allocateFrom(string);
long len = (long) strlen.invoke(str);
System.out.println("Finding String length using strlen function: " + len);
return len;
}
}
5.2. 类文件 API – JEP 457
类文件 API 标准化了 Java .class
文件的读取、解析和转换流程,并计划逐步替代 JDK 内部的 ASM 库。
该 API 提供强大的类元素和方法转换能力。示例:移除类文件中以 test_
开头的方法:
ClassFile cf = ClassFile.of();
ClassModel classModel = cf.parse(PATH);
byte[] newBytes = cf.build(classModel.thisClass()
.asSymbol(), classBuilder -> {
for (ClassElement ce : classModel) {
if (!(ce instanceof MethodModel mm && mm.methodName()
.stringValue()
.startsWith(PREFIX))) {
classBuilder.with(ce);
}
}
});
此代码解析源类文件字节码,仅保留满足条件的方法(MethodModel
类型),生成的新类文件将不包含原类的 test_something()
方法。
5.3. 流收集器 – JEP 461
JEP 461 通过 Stream::gather(Gatherer)
为 Streams API 引入自定义中间操作支持。由于内置中间操作有限,开发者长期需要扩展能力,此特性终于填补了空白。
通过在流上链式调用 gather()
并传入 java.util.stream.Gatherer
接口实例实现。
使用滑动窗口将列表元素按 3 个一组分组:
public List<List<String>> gatherIntoWindows(List<String> countries) {
List<List<String>> windows = countries
.stream()
.gather(Gatherers.windowSliding(3))
.toList();
return windows;
}
// 输入: List.of("India", "Poland", "UK", "Australia", "USA", "Netherlands")
// 输出: [[India, Poland, UK], [Poland, UK, Australia], [UK, Australia, USA], [Australia, USA, Netherlands]]
预览特性包含五个内置收集器:
fold
mapConcurrent
scan
windowFixed
windowSliding
该 API 还支持开发者自定义 Gatherer
。
5.4. 结构化并发 – JEP 462
结构化并发 API 在 Java 19 孵化,Java 21 预览,Java 22 无变更延续预览状态。
该 API 旨在为 Java 并发任务引入结构化与协调机制,通过特定编码模式减少并发编程的常见陷阱。
优势包括:
✅ 简化错误传播
✅ 减少取消延迟
✅ 提升可靠性与可观测性
5.5. 作用域值 – JEP 464
Java 21 引入的 作用域值 API 在 Java 22 进入第二次预览,无功能变更。
作用域值支持在线程内外存储和共享不可变数据,引入新类型 ScopedValue<>
。 值一旦写入即不可变。
典型场景:Web 请求处理。定义为公共静态字段后,无需显式参数即可跨方法传递数据对象。
示例:存储用户认证上下文:
private void serve(Request request) {
User loggedInUser = authenticateUser(request);
if (loggedInUser) {
ScopedValue.where(LOGGED_IN_USER, loggedInUser)
.run(() -> processRequest(request));
}
}
// 在另一个类中
private void processRequest(Request request) {
System.out.println("Processing request" + ScopedValueExample.LOGGED_IN_USER.get());
}
多用户登录时,用户信息会绑定到各自线程:
Processing request :: User :: 46
Processing request :: User :: 23
5.6. 向量 API(第七次孵化)– JEP 460
Java 16 引入的 向量 API 在 Java 22 进入第七次孵化,主要优化性能并更新底层实现。 此前向量访问仅限于字节数组支持的堆 MemorySegments
,现在更新为原始类型数组支持。
此为底层优化,不影响 API 使用方式。
6. 工具更新
6.1. 多文件源程序 – JEP 458
Java 11 支持单文件直接运行(无需 javac
),但无法处理依赖文件。Java 22 终于支持多文件程序直接运行:
public class MainApp {
public static void main(String[] args) {
System.out.println("Hello");
MultiFileExample mm = new MultiFileExample();
mm.ping(args[0]);
}
}
public class MultiFileExample {
public void ping(String s) {
System.out.println("Ping from Second File " + s);
}
}
直接运行 MainApp
无需显式编译:
$ java --source 22 --enable-preview MainApp.java "Test"
Hello
Ping from Second File Test
⚠️ 注意事项:
- 多文件编译顺序不保证
- 仅编译主程序引用的类文件
- 禁止重复类定义
- 可通过
–class-path
使用预编译库
7. 性能优化
7.1. G1 垃圾回收器区域钉住 – JEP 423
钉住(Pinning)机制通知垃圾回收器保留特定对象。不支持此功能的回收器需暂停回收,直到关键对象释放。
此问题在 JNI 关键区域尤为突出,影响延迟、性能和内存消耗。
Java 22 的 G1 垃圾回收器终于支持区域钉住,消除了 JNI 操作时暂停 G1 GC 的需求。
8. 总结
Java 22 带来了丰富的更新、增强和新预览特性,涵盖语言改进、库扩展、工具优化和性能提升。
所有代码示例可在 GitHub 获取。