JAVA 22:有什么新特性?

现在 Java 22 的功能已经完成,是时候介绍一下这个新版本为我们(开发人员)带来的所有功能了。

1、JEP 461 – 流收集器(预览)
通过支持自定义中间操作增强了 Stream API。这是一个预览 API。

流 API 提供了一组固定的中间和终端操作。它允许通过 Stream::collect(Collector) 方法扩展终端操作,但不允许扩展中间操作。有些操作是缺失的,有些则是可以通过一组操作实现的,有些则是通过不完全符合需要的操作实现的。

多年来,人们提出了许多新的终端操作,但即使大多数操作都很合理,也不可能将它们全部添加到 SDK 中。增加定义自己的中间操作的可能性可以缓解这个问题。

有了 JEP 461,现在就可以通过 Stream::gather(Gatherer) 定义自己的中间操作了。

gatherer 表示将流中的元素转换为一对一、一对多、多对一或多对多,并可在必要时停止转换,从而停止下游流中元素的排放。

gatherer 可以组合使用: stream.gather(...).gather(...).collect(...)。

var numbers = List.of(1, 2, 3, 4, 5);

var slidingWindows = numbers.stream()
    .gather(Gatherers.windowSliding(3))
    .toList();

System.out.println(slidingWindows);
// [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

java.util.stream.Gatherer 接口定义了以下方法:

  • initializer():可选,用于在处理元素时保持状态。
  • integrator():用于整合输入流中的新元素,必要时在下游流中发射一个元素。
  • combiner():可选,用于并行流中并行评估收集器。
  • finisher():可选,在流没有更多输入元素时调用。

流应用程序接口通过以下采集器得到了增强:

  • fold:有状态的多对一收集器,以增量方式建立聚合,并在没有更多输入元素存在时释放该聚合。
  • mapConcurrent:有状态一对一收集器,可为每个输入元素并发调用一个提供的函数,但以提供的限制为限。
  • scan(扫描):有状态的一对一收集器,将提供的函数应用于当前状态和当前元素,以生成输出元素。
  • windowFixed:有状态的多对多收集器,它将输入项分组到所提供大小的列表中,满后输出窗口。
  • windowSliding:有状态的多对多收集器,可将输入项按所提供的大小分组到列表中。在第一个窗口之后,每个后续窗口都是通过删除第一个元素并从输入流中添加下一个元素来创建的。

JavaDoc 提供了下面一个重现 Stream.map() 操作的收集器示例:

public <T, R> Gatherer<T, ?, R> map(Function<? super T, ? extends R> mapper) {
    return Gatherer.of(
        (unused, element, downstream) -> // integrator
            downstream.push(mapper.apply(element))
    );
}

2、JEP 458 - 启动多文件源代码程序
自 Java 11 起,您可以从 .java 源文件启动程序,而无需先对其进行编译。Java 启动器会在执行程序前自动在内存中编译程序。

有了 JEP 458,现在可以从使用另一个源文件中定义的类的源文件启动程序,这第二个文件也将在内存中自动编译。源文件在通常的 Java 目录层次中搜索,这反映了包结构。

只有主程序使用的源文件才会在内存中编译。

3、JEP 447 - super 之前的语句(预览)
当一个类扩展了另一个类并希望在自己的构造函数中调用父类的构造函数时,JVM 会强制将父类构造函数的调用作为父类构造函数的第一条指令。这样可以确保在构建子类之前,父类中的所有字段都已初始化。

JEP 447 是一项预览功能,允许在调用父类构造函数之前执行指令,只要这些指令不访问正在创建的实例即可。

JEP 中给出了几个例子:参数验证、参数预计算等。

下面是 JEP 447 之前的参数验证示例:

public class PositiveBigInteger extends BigInteger {

    public PositiveBigInteger(long value) {
        super(value);               // 可能不必要的工作
        if (value <= 0)
            throw new IllegalArgumentException(
"non-positive value");
    }

}

使用JEP 447 :

public class PositiveBigInteger extends BigInteger {

    public PositiveBigInteger(long value) {
        if (value <= 0)
            throw new IllegalArgumentException("non-positive value");
        super(value);
    }

}

这样的代码可读性更强,并有可能避免父构造函数的影响。

4、457 - 类文件 API(预览版)
JEP 457 提供了用于解析、生成和转换 Java 类文件Class-File 的标准 API。该 API 正在预览中。

Java 生态系统中有许多用于解析和生成 Java 类文件的库:ASM、BCEL、Javassist......大多数字节码生成框架都使用它们。

Java 类文件格式每 6 个月就会随 Java 新版本的发布而变化一次,因此生成框架也必须与时俱进,否则就有可能无法支持最新的语言发展。

JDK 本身使用 ASM 来实现其某些工具以及 lambda 实现,这就造成了一个 Java 版本的功能与 JVM 中需要生成类文件的部分所能使用的功能之间的差异,因为在 N+1 版本中使用这些功能之前,您必须等待支持 N 版本新功能的 ASM 版本。

类文件Class-File  API 通过在 JDK 中提供一个用于解析、生成和转换类文件的 API,克服了这一问题。

5、列表格式ListFormat
ListFormat 是一种新的格式化工具,可让您根据 Unicode 标准格式化与本地有关的字符串列表。

var list = List.of("Black", "White", "Red");
var formatter = ListFormat.getInstance();
System.out.println(list);
// [Black, White, Red]

在创建格式器时,我们可以将其传递给它:

  • 一个本地语言,否则将使用默认本地语言。
  • 枚举类型:STANDARD、OR 或 UNIT。默认为STANDARD。
  • 枚举样式:FULL, SHORT 或NARROW. 默认FULL.

6、预览版功能
预览版(或孵化器模块)的以下功能现已成为标准功能:

  • JEP 454 - 外来函数与内存 API:用于将 JVM 与本地代码互连的 API。
  • JEP 456 - 未命名变量和模式:允许将 _ 用作未命名模式或变量。

不过,"外来函数与内存 API "中的一个重要变化值得注意:引入了受限方法的概念。新 API 中的某些方法被标记为受限方法:要使用这些方法,需要使用 --enable-native-access=module-name 命令行选项。目前,访问受限方法会产生警告,但未来版本的 JVM 可能会禁止访问这些方法。受限方法用于绑定本地函数和/或本地数据,本质上是不安全的。因此,必须通过命令行选项才能访问受限方法。

7、仍处于预览状态的功能
以下功能仍处于预览状态(或在孵化器模块中)。

  • JEP-460 –矢量 API:第七次孵化,用于表达矢量计算的 API,这些计算在运行时编译为支持的 CPU 架构的矢量指令。这个新版本包括错误修复和性能增强。
  • JEP 464 –范围值:第二个预览版,支持在线程内和线程之间共享不可变数据。新预览版中没有明显变化。
  • JEP 462 –结构化并发:第二个预览版,一个新的 API,通过允许将多个并发任务视为单个处理单元来简化多线程代码的编写。新预览版中没有明显变化。
  • JEP 463 –隐式声明的类和实例主要方法:第二个预览版,通过允许在隐式类(无需声明)和实例方法中定义简单程序来简化简单程序的编写void main()。
  • JEP 459 –字符串模板:第二个预览,字符串模板是字符串的文字,可让您合并表达式和变量。此新预览版没有明显变化。

8、各种各样
JDK 的各种补充:

  • Console.isTerminal():如果控制台实例是终端,则返回 true。
  • Class.forPrimitiveName(String):返回与给定基本类型关联的类。
  • InetAddress.ofLiteral(String)InetAddress:根据 IP 地址的文本表示创建一个。Inet4Address和类也存在此静态方法Inet6Address。
  • RandomGenerator.equiDoubles(double left, double right, boolean isLeftIncluded, boolean isRightIncluded)。

所有新的 JDK 21 API 都可以在Java 版本年鉴 – Java 22 中的新 API中找到。

9、内部变更、性能和安全性
当 JNI(Java 本机接口)调用定义关键区域时,G1 垃圾收集器得到了改进。此前,G1 被完全禁用,存在阻塞需要 GC 的应用程序线程,甚至内存不足的风险。

得益于 JEP 423:G1 的区域固定,G1GC 现在能够在 JNI 关键部分发生时仅固定单个区域,从而避免阻塞需要 GC 的其他应用程序线程。更多信息请参见JEP 423

10、JFR活动
以下是 JVM 的新 Java Flight Recorder (JFR) 事件:

  • CompilerQueueUtilization:没有说明。
  • NativeLibraryLoad:有关动态库或其他本机图像加载操作的信息。
  • NativeLibraryUnload:有关动态库或其他本机映像卸载操作的信息。
  • DeprecatedInvocation:对用 注释的方法的唯一调用@Deprecated。

您可以在JFR 事件页面上找到此版本的 Java 支持的所有 JFR 事件。


结论
人们可能认为 Java 22 将是版本 21(即 LTS)之后的一个稳定版本,但事实并非如此,它以 Stream Gatherer 和许多旨在简化语言并使其更易于使用的 JEP 的形式进行了重大补充。另外值得注意的是,Foreign Function & Memory API 已不再预览,这将简化 Java 中本机函数的使用,并提供比 JNI 更易于使用的高性能 API。