Ohhnews

分类导航

$ cd ..
DZone Java原文

Java 25 新特性概览:从 Java 21 到 Java 25 的关键演进

#java 25#软件开发#编程语言#jdk#技术升级

2025 年 9 月 16 日,Java 25 正式发布。现在是时候深入了解自上一个长期支持版本(LTS)——Java 21 以来所发生的变化了。本博客主要通过示例来重点介绍 Java 21 到 Java 25 之间的一些变更。希望你喜欢!

引言

Java 21 和 Java 25 之间有哪些变化?完整的 JEP(Java 增强提案)列表可以在 OpenJDK 网站上找到,在那里你可以阅读每个 JEP 的详细内容。若要查看自 Java 21 以来每个版本变更的完整列表,Oracle 发行说明提供了很好的概览。

在接下来的章节中,我们将通过示例来解释其中的一些变化,但主要还是需要你自己去尝试这些新特性,以便熟悉它们。请注意,本文不讨论任何预览版或孵化器阶段的 JEP。本文使用的源码可在 GitHub 上获取。如果你想了解 Java 17 到 Java 21 之间的变化,请查看之前的博客;若想了解 Java 11 到 Java 17 之间的变化,请查看更早的博客

先决条件

阅读本博客的先决条件:

  • 你必须安装 JDK 25。我建议使用 SDKMAN,这样可以轻松切换不同的 JDK 版本。
  • 你需要具备一定的 Java 基础知识。

JEP 512:紧凑源文件与实例 main 方法

当你初学 Java 时,在开始编写代码之前,必须面对许多概念。看看经典的 HelloWorld.java,你会接触到以下概念:

  • 你必须理解“类”(class)的概念;
  • 你必须理解访问修饰符(本例中为 public);
  • 你必须知道什么是静态修饰符,以及静态与实例的区别;
  • 你必须知道什么是 void 返回类型;
  • 你必须知道什么是“字符串数组”(String Array);
  • 你必须了解神秘的 System.out

这确实很多!而你仅仅是为了在控制台输出一行简单的文本。JEP 512 的目的是移除所有这些样板代码,使 Java 的入门门槛更低。实际上这意味着:

  • 移除了静态方法,直接使用 void main
  • 无需创建“类”;
  • java.lang 包中引入了一个新的 IO 类,其中包含了基本的 IO 操作方法。

Java 25 中的新 HelloWorld 看起来是这样的: 是不是简单多了?请注意,这里甚至不允许使用包声明。其目的只是为了提供一个尽可能简单的起点来使用 Java。

JEP 513:灵活的构造函数体

在构造函数中,通常不允许在调用 this()(调用同一类中的另一个构造函数)或 super()(调用父类构造函数)之前添加语句。当你想要验证输入参数时,这会造成一定的限制。

假设有以下 Vehicle 类,类 Java21Car 继承了父类 Vehicle。这里存在两个问题:

  1. 如果子类第 7 行的条件结果为 true,则会不必要地调用父类 Vehicle 的构造函数。
  2. 如果你实例化一个 numberOfWheels 等于 1 的 Java21Car,会抛出 IllegalArgumentException,但被重写的 print 方法会输出 colornull,因为该值尚未赋值。

运行 FlexibleConstructor 类即可看到结果。

随着 JEP 513 的引入,这些问题可以得到解决。将子类构造函数中的验证代码移到 super() 调用之前。像以前一样创建实例,运行此代码会抛出 IllegalArgumentException,但这次不会调用 super(),因此也不会调用 print 方法。创建带有有效输入参数的实例时,super() 会被调用,print 方法将按预期输出数据。

JEP 456:未命名变量与模式

有时你并不使用某个变量。例如,在 catch 块中,你可能不想对抛出的 Exception 做任何处理。然而,在 Java 25 之前,强制要求给变量命名。

假设以下示例,其中 NumberFormatException 必须被命名为 ex。通过 JEP 456 的引入,你可以通过使用下划线将其声明为未命名变量,从而更明确地表达意图。

这同样适用于模式匹配。假设有以下类,你创建一个 switch 来测试实例属于哪种 Fruit。以前你必须给 case 元素命名,即使你不使用它们。现在,你同样可以使用下划线来使其更明确。

JEP 506:作用域值 (Scoped Values)

作用域值由 JEP 506 引入,主要用于框架代码与应用代码之间。典型的例子是处理 HTTP 请求,其中回调在框架代码中执行。handle 方法由框架内部调用,而应用代码则通过 readUserInfo 方法执行回调。

在框架中,数据存储在 Thread 内的框架上下文中。通过 ThreadLocal 创建 CONTEXT (1)。在调用应用代码之前,请求特定的数据被存储在该上下文中 (2)。当应用执行回调返回框架时,可以再次检索到 CONTEXT (3)。

使用 ThreadLocal 存在三个问题:

  1. 不受限的可变性:每个 ThreadLocal 变量都是可变的,如果代码能够调用 get 方法,它也就能够调用 set 方法。
  2. 无限的生命周期ThreadLocal 的值在线程的整个生命周期内存在,除非调用了 remove 方法。后者经常被遗忘,导致数据存在时间比应有的长。
  3. 昂贵的继承:当创建子线程时,ThreadLocal 变量的值会被复制到子线程中。子线程需要分配额外的存储空间。无法实现共享存储,当使用大量线程时,这可能会产生严重影响。

随着虚拟线程的引入,这些设计缺陷的影响愈发显著。解决方案就是作用域值。

作用域值是一个容器对象,它允许数据值在同一线程内被方法及其直接或间接调用的方法安全高效地共享,并能与子线程共享,而无需通过方法参数传递。它是一个 ScopedValue 类型的变量。通常声明为 static final 字段,并将其访问权限设为 private,以防止其他类中的代码直接访问。

之前的框架代码可以重写如下。不再创建 ThreadLocal 变量,而是创建一个 ScopedValue 类型的变量 (1)。在调用应用代码时,使用静态方法 ScopedValue.where 来分配值 (2)。readKey 方法保持不变 (3)。

主要优点是上下文值仅在 run 方法的生命周期内有效。通过结构化并发(使用 StructuredTaskScope)可以将数据复制到子线程,父线程的作用域值会自动在子线程中可用。结构化并发目前处于第 5 个预览阶段,因此很快就会正式可用。

JEP 485:流收集器 (Stream Gatherers)

流由三部分组成:1. 创建流;2. 中间操作;3. 终止操作。终止操作是可扩展的,但中间操作此前不可扩展。JEP 485 引入了一个新的中间流操作 Stream:gather(Gatherer),它可以使用用户定义的实体来处理元素。

创建收集器比较复杂,超出了本博客的范围,但以下讨论了一些内置收集器。

1. Gatherer: fold

fold 是一个“多对一”的收集器,它增量地构建一个聚合体,并在没有更多输入元素时发出该聚合体。

2. Gatherer: mapConcurrent

mapConcurrent 是一个有状态的“一对一”收集器,它为每个输入元素并发地调用提供的函数,直至达到指定的限制。

3. Gatherer: scan

scan 是一个有状态的“一对一”收集器,它将提供的函数应用于当前状态和当前元素,以生成下一个元素,并将其传递给下游。

4. Gatherer: windowFixed

windowFixed 是一个有状态的“多对多”收集器,它将输入元素分组为指定大小的列表,并在窗口填满时将其发射到下游。

5. Gatherer: windowSliding

windowSliding 是一个有状态的“多对多”收集器,它将输入元素分组为指定大小的列表。在第一个窗口之后,每个后续窗口都是通过丢弃前一个窗口的第一个元素并追加输入流的下一个元素来创建的。

Gatherers 结语

关于收集器,引用 Venkat Subramaniam 的四条建议:

  1. 使用熟悉的函数,如 mapfilter 等。
  2. 使用内置收集器。
  3. 向朋友寻求建议。
  4. 创建自己的收集器,但……这很复杂且工作量很大。

JEP 458:启动多文件源代码程序

当使用 Java 编写脚本时,如果脚本过长,你可能希望将代码拆分到不同的文件中。此外,编写脚本时,你很可能不会使用构建工具来创建 jar 文件。JEP 458 允许你在主类中无需额外配置即可解析所需的其他类。

在 Java 21 中,编译会失败,因为找不到与 Application 类位于同一位置的 Helper 类。切换到 Java 25 JDK,执行 Application.java 文件,此时 Helper 类可以被找到,程序按预期执行。

JEP 467:Markdown 文档注释

Java 21 中,你可以通过 HTML 标签格式化注释。然而,Markdown 也被开发人员广泛使用。JEP 467 允许你在文档注释中使用 Markdown。一些注意事项:

  • Markdown 注释以 /// 开头。
  • 不再需要 <p>,可以用空行代替。
  • 可以使用 Markdown 列表。
  • 字体变化使用 Markdown 语法,例如下划线表示斜体。
  • 反引号可用于代码字体。
  • 支持 Markdown 链接。
  • 所使用的 Markdown 语法为 CommonMark 标准。

结论

在本博客中,我们快速浏览了自上一个 LTS 版本 Java 21 以来添加的一些特性。现在,你需要考虑迁移到 Java 25 的计划,并探索如何学习这些新特性,以及如何将其应用到你的日常编码习惯中。提示:IntelliJ 会在这方面为你提供帮助!