JDK22
外部函数和内存 API
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
在没有外部函数和内存 API 之前:
-
Java 通过
sun.misc.Unsafe提供一些执行低级别、不安全操作的方法(如直接访问系统内存资源、自主管理内存资源等),Unsafe类让 Java 语言拥有了类似 C 语言指针一样操作内存空间的能力的同时,也增加了 Java 语言的不安全性,不正确使用Unsafe类会使得程序出错的概率变大。 -
Java 1.1 就已通过 Java 原生接口(JNI)支持了原生方法调用,但并不好用。JNI 实现起来过于复杂,步骤繁琐,不受 JVM 的语言安全机制控制,影响 Java 语言的跨平台特性。并且,JNI 的性能也不行,因为 JNI 方法调用不能从许多常见的 JIT 优化(如内联)中受益。虽然JNA、JNR和JavaCPP等框架对 JNI 进行了改进,但效果还是不太理想。
引入外部函数和内存 API 就是为了解决 Java 访问外部函数和外部内存存在的一些痛点。
Foreign Function & Memory API (FFM API) 定义了类和接口:
-
分配外部内存:
MemorySegment、MemoryAddress和SegmentAllocator; -
操作和访问结构化的外部内存:
MemoryLayout,VarHandle; -
控制外部内存的分配和释放:
MemorySession; -
调用外部函数:
Linker、FunctionDescriptor和SymbolLookup。
示例:使用FFM API调用C库中的strlen函数来计算字符串长度:
// 获取链接器和符号查找器
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
// 找到C库中的strlen函数
MemorySegment strlenSymbol = stdlib.find("strlen").get();
// 创建一个下调用方法句柄,它将strlen函数作为本地方法调用
MethodHandle strlenMH = linker.downcallHandle(strlenSymbol, FunctionDescriptor.of(JAVA_LONG, ADDRESS));
// 分配一个内存段来存储一个字符串
MemorySegment strSegment = Arena.allocateFrom("Hello, World! Ahzoo.cn");
// 调用strlen函数来获取字符串的长度
long stringLength = (long) strlenMH.invokeExact(strSegment);
// 输出字符串长度
System.out.println("String length: " + stringLength);
未命名模式和变量
未命名模式和变量使得我们可以使用下划线 _ 表示未命名的变量以及模式匹配时不使用的组件,旨在提高代码的可读性和可维护性。未命名变量的典型场景是 try-with-resources 语句、 catch 子句中的异常变量和for循环。当变量不需要使用的时候就可以使用下划线 _代替,这样清晰标识未被使用的变量。
示例:
try (var _ = ScopedContext.acquire()) {
// No use of acquired resource
}
try { ... }
catch (Exception _) { ... }
catch (Throwable _) { ... }
for (int i = 0, _ = runOnce(); i < arr.length; i++) {
...
}
未命名模式是一个无条件的模式,并不绑定任何值。未命名模式变量出现在类型模式中。
if (r instanceof ColoredPoint(_, Color c)) { ... c ... }
switch (b) {
case Box(RedBall _), Box(BlueBall _) -> processBox(b);
case Box(GreenBall _) -> stopProcessing();
case Box(_) -> pickAnotherBox();
}
以下场景可以使用未命名模式和变量(Unnamed Variables)
-
局部变量
-
try-with-resource
-
循环头中声明的变量
-
catch中声明的变量
-
lambda表达式中的参数
预览功能
-
灵活的构造函数体(预览)
-
字符串模板(第二次预览)
-
文件 API(预览)
-
流收集器(预览)
-
结构化并发(预览)
-
隐式声明的类和实例主方法(第二次预览)
-
作用域值(第二次预览)
JDK23
Markdown 文档注释
在 JavaDoc 文档注释中可以使用 Markdown 语法,取代原本只能使用 HTML 和 JavaDoc 标签的方式。

预览功能
-
模式中的原始类型、instanceof 和 switch(预览)
-
类文件 API(第二次预览)
-
流收集器(第二次预览)
-
模块导入声明 (预览)
-
未命名类和实例 main 方法 (第三次预览)
-
结构化并发 (第三次预览)
-
作用域值 (第三次预览)
-
灵活的构造函数体(第二次预览)
JDK24
类文件 API
类文件 API 的目标是提供一套标准化的 API,用于解析、生成和转换 Java 类文件,取代过去对第三方库(如 ASM)在类文件处理上的依赖。
示例:
// 创建一个 ClassFile 对象,这是操作类文件的入口。
ClassFile cf = ClassFile.of();
// 解析字节数组为 ClassModel
ClassModel classModel = cf.parse(bytes);
// 构建新的类文件,移除以 "debug" 开头的所有方法
byte[] newBytes = cf.build(classModel.thisClass().asSymbol(),
classBuilder -> {
// 遍历所有类元素
for (ClassElement ce : classModel) {
// 判断是否为方法 且 方法名以 "debug" 开头
if (!(ce instanceof MethodModel mm
&& mm.methodName().stringValue().startsWith("debug"))) {
// 添加到新的类文件中
classBuilder.with(ce);
}
}
});
流收集器
流收集器 Stream::gather(Gatherer) 是一个强大的新特性,它允许开发者定义自定义的中间操作,从而实现更复杂、更灵活的数据转换。Gatherer 接口是该特性的核心,它定义了如何从流中收集元素,维护中间状态,并在处理过程中生成结果。
与现有的 filter、map 或 distinct 等内置操作不同,Stream::gather 使得开发者能够实现那些难以用标准 Stream 操作完成的任务。例如,可以使用 Stream::gather 实现滑动窗口、自定义规则的去重、或者更复杂的状态转换和聚合。 这种灵活性极大地扩展了 Stream API 的应用范围,使开发者能够应对更复杂的数据处理场景。
基于 Stream::gather(Gatherer) 实现字符串长度的去重逻辑:
var result = Stream.of("ahzoo.cn", "ouo", "abc", "123", "十玖八柒")
.gather(Gatherer.ofSequential(
HashSet::new, // 初始化状态为 HashSet,用于保存已经遇到过的字符串长度
(set, str, downstream) -> {
if (set.add(str.length())) {
return downstream.push(str);
}
return true; // 继续处理流
}
))
.toList();// 转换为列表
// 输出结果 ==> [ahzoo.cn, ouo, "十玖八柒"]
预览功能
-
作用域值 (第四次预览)
-
简化的源文件和实例主方法(第四次预览)
-
结构化并发(第四次预览)
JDK25
作用域值
作用域值(Scoped Values)可以在线程内和线程间共享不可变的数据,这比线程局部变量更加方便,尤其是在使用大量虚拟线程时。
final static ScopedValue<...> V = new ScopedValue<>();
// In some method
ScopedValue.where(V, <value>)
.run(() -> { ... V.get() ... call methods ... });
// In a method called directly or indirectly from the lambda expression
... V.get() ...
作用域值通过其写入时复制(copy-on-write)的特性,保证了数据在线程间的隔离与安全,同时性能极高,占用内存也极低。这个特性将成为未来 Java 并发编程的标准实践。
紧凑源文件与实例main方法
这个改进极大地简化了编写简单 Java 程序的步骤,允许将类和主方法写在同一个没有顶级 public class的文件中,并允许 main 方法成为一个非静态的实例方法。
在没有该特性之前,我们定义一个 main 方法:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("十玖八柒");
}
}
在使用新特性后,我们就可以这样定义一个main方法:
class HelloWorld {
void main() {
System.out.println("十玖八柒");
}
}
甚至我们还可以进一步精简(未命名的类允许我们不定义类名):
void main() {
System.out.println("十玖八柒");
}
模块导入声明
模块导入声明允许在 Java 代码中简洁地导入整个模块的所有导出包,而无需逐个声明包的导入。这一特性简化了模块化库的重用,特别是在使用多个模块时,避免了大量的包导入声明,使得开发者可以更方便地访问第三方库和 Java 基本类。
此特性对初学者和原型开发尤为有用,因为它无需开发者将自己的代码模块化,同时保留了对传统导入方式的兼容性,提升了开发效率和代码可读性。
示例:
// 导入整个 java.base 模块,开发者可以直接访问 List、Map、Stream 等类,而无需每次手动导入相关包
import module java.base;
public class Example {
public static void main(String[] args) {
String[] ahzooStrs = { "ahzoo.cn", "十玖八柒", "ahzoo" };
Map<String, String> ahzooMap = Stream.of(ahzooStrs)
.collect(Collectors.toMap(
s -> s.toUpperCase().substring(0, 1),
Function.identity()));
System.out.println(ahzooMap);
}
}
灵活的构造函数体
Java 要求在构造函数中,super(...) 或 this(...) 调用必须作为第一条语句出现。这意味着我们无法在调用父类构造函数之前在子类构造函数中直接初始化字段。
灵活的构造函数体解决了这一问题,它允许在构造函数体内,在调用 super(..) 或 this(..) 之前编写语句,这些语句可以初始化字段,但不能引用正在构造的实例。这样可以防止在父类构造函数中调用子类方法时,子类的字段未被正确初始化,增强了类构造的可靠性。
这一特性解决了之前 Java 语法限制了构造函数代码组织的问题,让开发者能够更自由、更自然地表达构造函数的行为,例如在构造函数中直接进行参数验证、准备和共享,而无需依赖辅助方法或构造函数,提高了代码的可读性和可维护性。
示例:
class Person {
private final String name;
private int age;
public Person(String name, int age) {
if (!name.equals("ahzoo")) {
throw new IllegalArgumentException("Error Name!");
}
this.name = name; // 在调用父类构造函数之前初始化字段
this.age = age;
// ... 其他初始化代码
}
}
class Employee extends Person {
private final int employeeId;
public Employee(String name, int age, int employeeId) {
this.employeeId = employeeId; // 在调用父类构造函数之前初始化字段
super(name, age); // 调用父类构造函数
// ... 其他初始化代码
}
}
预览功能
-
模式匹配支持基本类型 (第三次预览)
-
结构化并发(第五次预览)
-
向量 API(第十次孵化)
-
基本类型模式匹配(第三次预览)
这里还没有评论哦
快来发一条评论抢占前排吧