JDK 22有哪些新功能?

从今天起,Java 将进入下一个 "降级 "阶段--这意味着功能列表已被冻结,预计不会再有新功能。因此,我们将查看完整的功能列表。

JEP 423:G1的区域引脚
JEP 423 的设计目的是通过引入 "区域锁定 "来减少 G1 的延迟。这可确保在 Java 本地接口的关键区域无需关闭垃圾回收进程。

JNI 中的关键区域是线程与 Java 对象直接交互的特定代码段。在该区域内,线程可以直接更改对象的数据,这在与 C 和 C++ 等 "非托管 "编程语言交互时至关重要。原始的 JEP 对这一概念提供了非常易懂的解释。

它们解决了什么问题? 当线程处于 JNI 临界区时,JVM 必须在垃圾回收过程中防止对象在临界区内移动。G1 关闭了每个临界区域的 GC,这极大地影响了 JNI 代码的延迟。

解决方案JEP 通过提供固定任何区域的功能来增强 G1。这个过程包括维护每个区域的临界对象列表:当临界对象被获取时,计数会增加,而当临界对象被释放时,计数会减少。计数不为零的区域被视为 "固定 "区域,在 GC 时不会被清除。

JEP 454:外来函数和内存 API
JEP 454 的目标是用更直接、更易懂的纯 Java 应用程序接口来替代 JNI。它还力求提高性能,提供广泛的平台兼容性,并确保本地代码和数据操作的一致性和完整性。

它们能解决什么问题?Java 开发人员经常需要与 JVM 不直接管理的资源进行交互,特别是用 C 语言编译的代码和本地内存中存储的数据。然而,现有的应用程序接口(API)并没有提供直接安全的方法来实现这一点。这种限制阻碍了与其他平台的顺畅交互以及对本地库和数据的利用,而这正是 Python 等语言的优势所在。

解决方案JEP 454 提出了外部和内存函数(FFM API)API,作为这些问题的解决方案。该 API 使 Java 程序能够调用外部函数(不在 JVM 控制范围内的代码)并安全地访问外部内存(不在 JVM 管理范围内的内存),从而替代不稳定、不安全且普遍不受欢迎的 JNI。该应用程序接口的目标是允许 Java 程序以更可靠(引入类型)、更灵活的方式调用本地库和处理本地数据。

JEP 456:无名变量与模式
JEP 456 旨在通过允许开发人员 "标注 "在某些上下文中需要创建的变量(即使这些变量可能不会实际使用),来提高代码的可读性和可维护性。此外,它还旨在改进 switch 和 case 语句中模式的处理。

最初的问题:开发人员通常会声明他们不打算使用的变量,这可能是出于风格的考虑,也可能是由于编程语言的要求。他们认为这些变量不会被使用,这种心照不宣的想法可能会导致代码中的错误和不确定性。它们还可能会误导各种精简器和其他代码辅助工具,而这些工具正变得越来越普遍。

解决方案JEP 456 引入了 "未命名 "变量和模式。当需要变量声明或嵌套模式时,可以使用这些变量和模式,但它们永远不会被使用。这两种变量都用下划线字符 _ 表示。这清楚地表明变量或 lambda 参数未被使用,从而提高了代码的可读性和可维护性,并提供了更好的工具支持。

在实践中:

未命名变量

for (Order _ : orders) { 
    total++; 
}

未命名模式

switch (obj) { 
     case Integer _: 
        System.out.println("This is an Integer, but its value is not needed."); 
        break
     
// Other cases... 
}

JEP 458:启动多文件源代码程序
JEP 458 简化了从开发基本程序逐步过渡到开发更复杂程序的过程。它能让你毫不费力地构建和执行由多个源文件组成的应用程序,而无需对每个源文件进行手动编译。

隐式声明的类和实例主方法(第二次预览)》,可稍往下查看。

它们解决了什么问题? 过去,执行用 Java 编写的程序必须先将源代码编译成 .class 文件,然后才能运行这些程序。JEP 330 允许直接执行单个源文件,但是,整个程序必须包含在一个 .java 文件中,众所周知,在 JVM 世界中...

解决方案:JEP 458 增强了使用 java 命令运行程序的能力,使以多个 Java 源文件交付的程序得以执行。现在,您无需编译即可运行由多个 .java 文件组成的程序,从而简化了脚本编写过程。

在实践中:如果我们有两个文件:Main.java 和 Helper.java,可以使用 java Main.java 运行 Main.java,程序会自动找到并编译 Helper.java(某些情况下可能需要定义目录结构或 classpath)。

以下预览:
JEP 447: Statements before super(...) (Preview)
本 JEP 引入了 "前构造函数上下文pre-constructor context "的概念。这既包括 super 明确调用构造函数的参数,也包括在此之前发生的所有指令。该上下文的规则与普通实例方法的规则类似,但代码不能引用正在创建的实例。

过去,构造函数要求初始语句调用父类的构造函数 super(...) 或同类的另一个构造函数 this(...)。这种限制给整合必要的逻辑以生成 super 的参数带来了挑战,这些参数必须位于静态辅助方法、间接辅助构造函数或构造函数参数中。

解决方案JEP 447 对构造函数的语法进行了修改,允许在显式构造函数调用之前定位不引用被构造实例的指令。这样做的目的是在定义构造函数的行为时提供更大的灵活性,同时确保构造函数在创建类实例的过程中 "自上而下 "地发挥作用。

class SubClass extends SuperClass {
   SubClass(int param) {
     // Logic before constructor
    int calculatedValue = someStaticMethod(param);
    super(calculatedValue);
// Wywołanie konstruktora klasy nadrzędnej

   
// Additional logic after executing super(...)
   }

   private static int someStaticMethod(int param) {
      return param * 2;
 }}

JEP 457: Class-File API (Preview)
JEP 457 的目的是为解析、生成和更改 Java 类文件提供一个标准 API。

在 Java 生态系统中,类文件对于解析、生成和转换至关重要。然而,当前的类文件处理库(如 ASM、BCEL 或 Javassist)往往难以跟上 JDK 中引入的类文件格式的快速变化。这就导致了版本不兼容问题和应用程序开发人员可以看到的错误。

解决方案JEP 457 提出了一种标准 API,用于根据 JVM 规范概述的类文件格式解析、生成和更改 Java 类文件。这将使 JDK 组件能够完全过渡到它,从而无需在 JDK 中使用 ASM 库的内部副本。

为什么现在要这样做?开发人员认为,JVM 的发展速度明显加快,越来越多的修改直接发生在生成的代码级别。他们认为,随着新 API 的引入,像 Valhalla 这样的大型项目对整个生态系统造成的干扰将会减少。

CodeModel code = ...
Set<ClassDesc> deps = new HashSet<>();
for (CodeElement e : code) {
    switch (e) {
        case FieldInstruction f  -> deps.add(f.owner());
        case InvokeInstruction i -> deps.add(i.owner());
        // ... i tak dalej dla instanceof, cast, itd.
    }
}


JEP 459: String Templates (Second Preview)
JEP 459 将字符串模板作为 Java 中一种全新的表达式类别。文本模板通过将硬编码文本与集成表达式和模板处理器合并以实现预填充结果,从而帮助生成冗长的字符串。

主要问题是 Java 程序员经常通过将固定文本与表达式相结合来构造字符串。目前 Java 中的字符串连接方法(如使用 + 运算符)并不方便,而且可能导致代码难以维护。另一方面,String.join 的功能也不够强大,无法满足复杂的使用要求。

解决方案字符串模板(String Templates)是 Java 中的一种新语言结构,与以前的方法相比,它能以更复杂的方式促进字符串插值。它们提供了一种安全高效的字符串组成方法。例如,STR 模板处理器通过用适当格式化的值替换模板中的每个嵌入表达式来执行字符串插值。

String name = "Joan";
String info = STR.
"My name is \{name}";
assert info.equals(
"My name is Joan");   // true

JEP 461: Stream Gatherers (Preview)
流收集器接口表示流元素的转换。采集器可以通过多种方式转换元素:一对一、一对多、多对一、多对多。收集器还能保存以前遇到的元素记录,以影响后续元素的转换。此外,它们还能停止处理,将无限流转化为有限流,从而促进并行执行。

它们能解决什么问题?尽管 JDK 1.8 中的流应用程序接口提供了映射、过滤、还原和排序等一系列中间和最终操作,但它缺乏扩展这一系列操作的能力。这意味着,由于缺乏所需的中间操作,某些更复杂的任务无法定义为流。

JEP 的贡献:JEP 461 为流应用程序接口提供了中间操作 Stream::gather(Gatherer),使流元素能够通过一组用户指定的操作(称为 "收集器")进行处理。这增强了流 API 的功能,现在它几乎可以映射任何中间操作。

代码示例假设任务是获取字符串流并使其具有唯一性,但唯一性是基于字符串的长度而不是其内容:

var result = Stream.of("foo", "bar", "baz", "quux")
        .gather(new DistinctByLengthGatherer())
        .toList();                   

public class DistinctByLengthGatherer implements Gatherer<String, String, Set<Integer>, List<String>> {
    @Override
    public Supplier<Set<Integer>> supplier() {
        return HashSet::new;
    }

    @Override
    public BiConsumer<Set<Integer>, String> accumulator() {
        return (seen, string) -> {
            if (seen.add(string.length())) {
                seen.add(string);
            }
        };
    }

    @Override
    public BinaryOperator<Set<Integer>> combiner() {
        return (seen1, seen2) -> {
            seen1.addAll(seen2);
            return seen1;
        };
    }

    @Override
    public Function<Set<Integer>, List<String>> finisher() {
        return ArrayList::new;
    }
}

在本例中,假设操作 .gather(newDistinctByLengthGatherer())就是 Gatherer 应用程序的一个示例。

JEP 462: Structured Concurrency (Second Preview)
JEP 462 提出了结构化并发的应用程序接口(API),它简化了对在不同线程中作为单一工作单元运行的相关任务组的管理。这种方法增强了错误处理和取消功能,从而提高了可靠性和可观测性。

JEP 的贡献:并发编程可能很复杂,尤其是在管理同时执行的多个任务时。当前的方法经常面临线程泄漏、取消延迟以及难以观察并发代码等潜在危险。

解决方案结构化并发 API 的主要类是 java.util.concurrent 包中的 StructuredTaskScope。通过该类,您可以将任务组织成一组并发子任务,并将它们作为特定结构中的单元进行管理。这些子任务在各自的线程中运行,然后被控制、组合和取消,包括级联取消。

Response handle() throws ExecutionException, InterruptedException {
  try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Supplier user = scope.fork(() -> findUser());
    Supplier order = scope.fork(() -> fetchOrder());
    scope.join()            
         .throwIfFailed(); 

    return new Response(user.get(), order.get());
  }
}

JEP 463: Implicitly Declared Classes and Instance Main Methods (Second Preview)
增强了直接在源文件级别编写主方法的能力,而无需将其放在类中。这简化了代码结构,尤其适合新手。

它们能解决什么问题?尽管 Java 在构建大型复杂应用程序方面非常有效,但它也会给初学者带来困难,尤其是在与 Python(再一次)进行对比时。在学习 Java 的初始阶段,往往需要理解一些编写基本程序时不需要的复杂概念。

解决方案JEP 463 在 Java 中引入了隐式声明的类和实例的主方法,简化了编写基本程序的过程。它为创建初始程序提供了一种更友好、更简单的方法,同时确保了完全的兼容性,并有可能随着程序员技能的提高而扩展到更复杂的语言功能。

void main() {
    System.out.println("Hello, World!");
}

本例说明了如何通过简化结构编写一个显示 "Hello, World!"的程序,而无需明确声明类和静态 main 方法。

JEP 464: Scoped Values (Second Preview)
JEP 464 引入了范围值(Scoped Values),可在特定范围内安全高效地传输 "封闭 "数据,确保单线程内和线程间的不变性。它们是 Thread.Local 的首选替代品,尤其是在使用大量虚拟线程时。

主要问题:在 Java 中,方法通常需要将数据作为参数传递,当每次间接调用都需要不同的数据时,这可能是不切实际的。因此,通常会创建一个可变上下文,它是一种'数据包',通过参数传递或保存在线程内存中"。

解决方案Scoped values(作用域值)可以在综合程序中实现方法间的数据共享,无需使用方法参数。这些值属于 ScopedValue 类型,通常声明为 final static private,以防止其他类直接访问。

ScopedValue.where(NAME, <value>)
           .run(() -> { ... NAME.get() ... call methods ... });

在本例中,ScopedValue.where 用于定义作用域值及其应绑定的对象。调用 run 时,它会绑定作用域值,创建当前线程专用的副本,然后执行传递的 lambda 表达式。在处理运行调用时,lambda 表达式或直接或间接从该表达式调用的任何方法都可以使用 get() 方法读取作用域值。

以下孵化阶段
JEP 460: Vector API (Seventh Incubator)
JEP 460 扩展了允许表达矢量计算的应用程序接口,在支持的 CPU 架构上,矢量计算可编译为本地矢量指令,其性能超过同等的标量计算。

它们解决了什么问题?"矢量计算 "可同时对多个数据执行操作,这一概念在 Java 中的表述具有挑战性。它依赖于 HotSpot 自动矢量化算法,这反过来又限制了它的实际可用性和性能。

解决方案Java 中的矢量 API 使复杂矢量算法的开发具有更强的可预测性和可靠性。它利用现有的 HotSpot 自动矢量化算法,为用户提供了一个更可预测的模型。

自上次孵化器以来的变化:在 JDK 22 中重新引入了 API 进行孵化,并在 JDK 21 的基础上进行了小幅增强,如错误修复和性能提升。预计 Valhalla 项目将在预览版之前完成。