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() 调用前放置任何语句。假设存在继承自 ShapeSquareCircle 类,子类构造函数的首行必须是 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 获取。


原始标题:Introduction to Java 22