JDK 24 来了!每个 Java 开发人员都必须了解的改变游戏规则的功能

在本文中,我概述了 JDK 24 中引入的最重要的语法和 API 更新。

JDK 24 是一个重要的版本,因为包含在此更新中的 JDK Enhancement Proposal(JEP) 候选项有更大的机会成为 JDK 25 的一部分,JDK 25 计划于 2025 年 9 月发布。

JDK 25 将是继 JDK 21(当前的长期支持 (LTS) 版本)之后的下一个长期支持 (LTS) 版本。

JDK 24 对 Java 编程语言的性能和语法结构都有显著改进。让我们来探讨一些最重要的更新。

⚠️ 这里提供的所有代码示例都是 JDK 24 的预览功能。要运行这些示例,您需要使用 --enable-preview 标志启用预览模式。

以下是文章中使用的符号指南:

🔗 — Link to JEP

⚠️ — Pay attention

❌ — Limitation

👉 — Reasoning

1. 简单源文件和实例主方法

终于来了!Java 现在允许使用 void main() 方法打印 “Hello, World”。

在 JDK 21 之前,传统的 “Hello, World “程序如下所示:

public class MyFirstClass {
    public static void main(String[] args) {
        System.out.println("Hello, World");
    }
}

❌Java 初学者面临的挑战之一是代码过于冗长,难以理解,尤其是对那些不熟悉 Java 语法和结构的人来说。

有了 JDK 24,”Hello, World “程序就简单多了,看起来像这样:

void main() {
    println("Hello, World");
}

⚠️这不是一种新的 Java 语法–有经验的 Java 开发人员仍可使用所有现有的语法和冗余。

👉通过 JVM 对类和实例方法的隐式声明,这种新语法成为可能。

此外,JVM 现在会在运行时自动导入某些必需的包,从而减少了在使用 println() 等实用程序方法时手动声明命名空间的需要。

下面的程序从控制台读取用户名并打印问候信息。

void main() {
    var name = readln();
    var message = "Hello, World and " + name;
    println(message);
}

如果我们转而使用实际的/冗长的 Java 代码,那么这段代码将转换如下:

import java.io.*;

public class A {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String name = sc.nextLine();
        String message = "Hello, World and " + name;
        System.out.println(message);
        sc.close();
    }
}

这也改进了简单 Java 程序的编码,而无需依赖繁冗的代码和声明。

🔗 JEP 495 中介绍了简单源文件和实例主方法。

2. 用于类型匹配的 Switch-Case 的改进

❌ 在 JDK 21 之前,switch-case 语句不能对变量执行类型匹配。相反,我们必须使用 if-else 条件,如下所示。

public class Test {
    public static String identifyType(Object value) {
      if (value instanceof Integer) {
          return String.format("Integer: %d", value);
      } else if (value instanceof String) {
          return String.format("String: %s", value);
      } else if (value instanceof Double) {
          return String.format("Double: %f", value);
      } else {
          return "Unknown type";
      }
}

👉引入switch类型匹配后,代码变得更加简洁易读,如下所示:

void main() {

String identifyType(Object value) {
        return switch (value) {
                case Integer i -> String.format("Integer: %d", i);
                case Double d -> String.format("Double: %f", d);
                case String s -> String.format("String: %s", s);
                default -> "Unknown type";
            };
}

🔗JEP 488 中介绍了模式、instanceof switch 中的原始类型。

3. 灵活的构造函数体

👉 通过该 JEP,开发人员可以在使用 super() 初始化超类之前在构造函数体中包含逻辑。

目前,在 JDK 21 中,这是不可能的–开发人员必须在初始化后使用超类中的辅助方法来执行逻辑。

构造函数主体现在分为两部分: 序幕和尾声。

public class A extends B{
    public A() {
        // Prologue
        super();
        // Epilogue
    }
}

因此,我们现在可以在 super() 调用之前和之后定义逻辑,但在序幕期间不能访问正在初始化的 ⚠️ 当前实例。

下面是一个示例。

public class A extends B {
    private static int multiplier = 10;
    private final int childValue;

    public A(int baseValue) {
        // Prologue
        int computedValue = baseValue * multiplier;

        super(baseValue); // Initialize B

        // Epilogue
        this.childValue = computedValue;
    }
}

🔗灵活构造体在 JEP 492 中介绍。

4. ScopedValues

ScopedValue API 是对 ThreadLocal API 的改进,用于跨不同线程(包括虚拟线程)和任务共享不可变数据。

ScopedValues 解决了 ThreadLocal 面临的三个主要挑战:

  1. 可变性: 在 ThreadLocal 中,线程既可以访问也可以修改共享变量,从而导致意想不到的副作用。而 ScopedValue 只允许共享不可变数据。
  2. 非绑定寿命: ThreadLocal 变量不再需要时必须手动删除。如果被多个线程访问,它在内存中的持续时间可能会超过所需的时间。ScopedValue 会在原始拥有线程执行完毕后自动清理。
  3. 昂贵的继承: 在 ThreadLocal 中,任何从父线程分叉出来的子线程都必须在内存中复制和重新创建变量。ScopedValue 允许父线程和子线程有效共享内存。

下面是一个使用 ScopedValue API 的简单示例。

static final ScopedValue<String> USERNAME = ScopedValue.newInstance();

void main(String[] args) {
        ScopedValue.where(USERNAME, "Amrit").run(() -> {
            System.out.println("Value inside main task: " + USERNAME.get());
            performSubTask();
        });
}

static void performSubTask() {
        System.out.println("Value inside sub-task: " + USERNAME.get());
}

🔗ScopedValues 在 JEP 487 中引入。

5. 模块导入声明

👉 通过模块导入声明,用户现在可以直接导入一组相关的包,而不是逐个导入。

这不仅减少了冗余,还消除了因重复导入包而造成的低效。

例如,文件顶部不再有以下声明。

import javax.xml.*;
import javax.xml.parsers.*;
import javax.xml.stream.*;

现在,用户只需导入按 java.xml 模块分组的所有这些软件包,如下所示。

import module java.xml;

👉还有其他一些模块,如 java.basejava.desktopjava.sql 等,它们为每个用例组合了所需的包。

🔗模块导入声明在 JEP 494 中介绍。

6. 结构化任务 API

StructuredTask API 将并发编程中的子任务组合在一起,在运行时将它们视为一个单元,而不是单独、独立的任务。

❌ 在使用 ExecutorService API 的传统并发执行中,子任务与启动它们的父任务只有逻辑关系。然而,在运行时,它们是独立运行的,没有直接联系。

这种结构上的缺失带来了一个挑战–如果一个子任务依赖于另一个子任务,并且依赖的子任务失败了,那么第一个子任务也应该失败。但是,在依赖任务完成之前,并没有内置机制来跟踪其状态或执行故障传播。

👉有了 StructuredTask API,开发人员现在可以控制整个任务,而整个任务可以由在不同任务中作为一个整体运行的子任务组成。

因此,如果一个任务失败,失败会传播到其他从属任务,整个任务也会中止。

下面的示例解释了 ExecutorService API 和 StructuredTask API 之间的对比。

import java.util.concurrent.*;

Integer fun1() throws Exception {
        Integer val = 1;
        for (int i = 0; i < 10; i++) {
                println(val);
                val++;
                Thread.sleep(1000);
                if (val == 4) {
                        System.out.println("Fun 1 stopped at value 4");
                        throw new Exception();
                }
        }
        return val;
}

Integer fun2() throws InterruptedException {
        Integer val = 1;
        for (int i = 0; i < 10; i++) {
                println(val);
                val++;
                Thread.sleep(1000);
        }
        return val;
}

void handle() throws ExecutionException, InterruptedException {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
                // Grouping using Scope
                Supplier<Integer> x = scope.fork(() -> fun1());
                Supplier<Integer> y = scope.fork(() -> fun2());
                scope.join().throwIfFailed();
        } catch (Exception e){
                println("Both Fun 1 and Fun 2 is now stopped because Fun 1 failed");
        }
}

void handleWithoutST() throws ExecutionException, InterruptedException {
        ExecutorService exsc = Executors.newFixedThreadPool(3);
        Future<Integer> x = exsc.submit(() -> fun1());
        Future<Integer> y = exsc.submit(() -> fun2());
        exsc.shutdown();

}

void main() throws ExecutionException, InterruptedException {
//      handle();          // --> Stops with either of the sub-task fails
//      handleWithoutST(); // --> Does not stop and keeps execution alive
}

🔗结构化任务在 JEP 499 中介绍。

7. 超前类加载和链接

超前(AOT)类加载和链接旨在改善 Java 应用程序的启动时间。

它通过在一次运行中缓存类的加载和链接形式并在后续运行中重复使用来实现这一目标。

该增强功能还为今后优化启动和预热时间奠定了基础。

👉 在 Java 程序中,超前编译的性能提高了 40%。

🔗 JEP 483 中引入了超前 (AOT) 类加载和链接。

更多 JDK 24 更新

在本文中,我总结了最重要的更新,我认为这些更新对 Java 开发人员非常有用,并将它们引入到日常编码中。

🔗 不过,还有更多 JEP 是 JDK 24 的预览候选版本,您可以从 JDK 24 的官方发布页面 https://openjdk.org/projects/jdk/24/ 进行查看。

你也许感兴趣的:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注