Java 继续保持着快速且功能丰富的发布节奏,JDK 25 带来了语法改进、简化以及性能增强。本文将探讨 Java 25 的核心更新,并附上代码和使用示例。JDK 25 现已成为最新的长期支持(LTS)版本,其中包含众多值得探索的新特性。
更简单的源文件和实例主方法
多年来,Java 一直致力于摒弃冗长的语法,朝着更加简洁的方向发展。对于 Java 初学者来说,一个长期存在的难题就是定义一个简单的"Hello World"程序时所涉及的繁琐步骤:
java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
对于初学者来说,要么就得忽略诸如 "public"、"class" 和 "static" 这类晦涩的词汇,要么就得先了解可见性、类以及静态成员的概念,然后才能开始学习 Java 语言的基本语法。
Java 25 通过 JEP 512 解决了这一问题:压缩源文件和实例主方法,这使得我们能够以如下方式编写相同的程序:
java
void main() {
IO.println("Hello, World!");
}
最明显的一点是,所有的面向对象编程语法都已消失。当然,也可以根据需要将前面完整的代码重新添加进来。另外一个注意的是 "IO.println()" 的调用,IO类 Java 25 新增的类,其中提供的静态方法println,就是对原先 System.out.println 的调用,且 IO 类是 "java.lang" 的一部分,无需显式导入即可使用。这解决了 Java 开发者长期以来的一个不满之处,即仅仅为了向控制台输出就需要编写多行代码。
新的灵活构造方法
又一项关于灵活性胜过形式性的胜利------JEP 513:灵活的构造方法体在 Java 25 中得以实现,这一成果历经了漫长的孕育过程。灵活的构造方法,顾名思义,使构造方法的设定不再那么僵化。具体而言,Java 不再要求在构造方法中调用 super() 或 this() 必须在第一行 。
此前,即便没有显式地调用 super() 或 this() 方法,编译器也会自动行这些操作。而且,如果在其他地方尝试调用它们,也会引发错误。而现在这一切都变得更加灵活了。
仍然有一些规则需要遵守,但现在可以在调用 super() 之前在类自身上运行初始化代码。这样可以避免出现这样的情况:由于子类的初始化过程省去了超类的初始化过程,所以可能会需要额外的工作。
要全面理解这种构造方法的好处,不妨考虑这样一个例子:假设有一个名为Shape的父类,并且希望该构造方法能够接受一个面积参数:
java
class Shape {
final int area;
public Shape(int area) {
if (area <= 0) {
throw new IllegalArgumentException("Area 必须是正数.");
}
this.area = area;
}
}
现在假设想要创建一个"Rectangle"类。在 Java 25 之前,必须想办法将相关计算提取出来,以便在 super() 调用中使用,通常会采用静态方法的方式:
java
// 之前的写法
class Rectangle extends Shape {
private static int checkAndCalcArea(int w, int h) {
if (w <= 0 || h <= 0) {
throw new IllegalArgumentException("宽高必须是正数.");
}
return w * h;
}
public Rectangle(int width, int height) {
super(checkAndCalcArea(width, height)); // super() 必须是第一行
// 其他语句
}
}
这段代码确实有些繁琐。但在 Java 25 版本中,更容易理解我们的意图,并且可以在 Rectangle 构造方法中进行面积计算:
java
class Rectangle extends Shape {
final int width;
final int height;
public Rectangle(int width, int height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("宽高必须是正数.");
}
int area = width * height;
super(area); // Java 25之前,这里有语法错误
this.width = width;
this.height = height;
}
}
批量模块导入
Java 25 中最终确定的另一项功能,JEP 511:模块导入声明,允许一次性导入整个模块,而无需逐个导入每个包。
尽管存在一些细微差别(比如需要明确解决包名冲突的问题),但其基本理念是:无需像以往那样必须导入模块中的每一个包,而是可以直接导入整个模块(包括其中的所有包)。JEP 中的这个示例展示了旧的模块内容导入方式:
java
import java.util.Map; // 或 import java.util.*;
import java.util.function.Function; // 或 import java.util.function.*;
import java.util.stream.Collectors; // 或 import java.util.stream.*;
import java.util.stream.Stream; // (可以删除)
String[] fruits = new String[] { "apple", "berry", "citrus" };
Map<String, String> m =
Stream.of(fruits)
.collect(Collectors.toMap(s -> s.toUpperCase().substring(0,1),
Function.identity()));
现在只需输入:
java
import java.base; // 导入包含所需包的模块
ScopedValue
ScopedValue 可作为线程局部变量的替代选择,是一种便捷且高效的替代方案,尤其适用于虚拟线程和结构化并发场景。
使用线程局部变量的一个常见例子出现在网络应用程序中,这里我们希望将数据仅存储在特定线程的内存中。例如,可以恢复用户数据并将其存储在内存中,以便后续执行的任何业务逻辑能够使用该数据。
在使用虚拟线程的情况下,线程局部变量的缺点会更加明显。使用虚拟线程的应用程序可能会生成数百甚至数千个并发的虚拟线程,而父线程将与所有这些虚拟线程共享线程局部变量。线程局部变量还会在整个线程的生命周期内存在,并且是完全可修改的。
Java 25 中的 "ScopedValue" 是不可变的,并且仅在调用方法的生命周期内存在。这意味着它们可以被有效地共享给任意数量的虚拟线程,并且开发人员可以轻松地定义其生命周期。
作用域内的值允许声明共享值,并传入一个函数来启动后续的所有操作:
java
ScopedValue.where(NAME, <value>).run(() -> {
//... NAME.get() ... 使用NAME调用方法 ...
}
在这种情况下,NAME 本身就是作用域内的值:
java
static final ScopedValue<...> NAME = ScopedValue.newInstance();
模式中的基本类型
在 Java 25 中仍处于预览状态的 JEP 507 允许在模式、instanceof 以及 switch 语句中使用诸如 int 和 double 这样的基本类型。尽管这看起来只是一个小的改进,但它却是朝着最终在 "Valhalla项目" 中将基本类型和对象统一起来迈出的又一步。
Valhalla 是由 Oracle 公司主导、OpenJDK 社区维护的一个重要项目,旨在为Java平台引入新的内存模型和值类型特性。该项目的核心目标是通过增强Java虚拟机(JVM),实现更高效的数据表示和处理方式,特别是通过引入值类型来减少对象的开销。
紧凑对象头
紧凑对象头(JEP 519)可提升 JVM 的内存性能。在更新到 Java 25 版本时将自动获得此功能,无需进行任何额外操作。它能减少内存开销,对于某些应用程序而言,这可能会带来显著的性能提升。
对象头存储了 JVM 用于在内存中描述对象元数据的信息。此次更改通过巧妙的编码方式将对象头压缩至更小的占用空间。这种在内存效率上的提升还减少了垃圾回收的频率。
分代垃圾回收
尽管 G1 收集器仍是主流选择,但 Shenandoah 选项在服务器应用中因低延迟需求而颇受欢迎。它避免了垃圾回收期间可能出现的延迟问题,并且最近还进行了更新,加入了分代垃圾回收功能。使用 Shenandoah 时,此功能已成为标准配置。
虽然大多数堆中的对象都能迅速被分配和释放,但长期存在的对象则较为少见。分代垃圾回收机制正是利用了这一特点:它先在堆中分配内存,然后在一段时间后对其进行清理,以移除未使用的对象。之后,它会将长期存在的对象迁移到更长期的回收区域。
一般来说,当需要最大的吞吐量(即整体性能)时,应采用 G1 默认设置。如果需要最低的延迟,即尽可能减少垃圾回收的暂停次数,那么可以考虑使用 Shenandoah。
要使用 Shenandoah,运行 Java 时请添加以下开关:
java -XX:+UseShenandoahGC
结论
当我刚开始使用 Java 时,它还是一门新兴的语言,但我很欣赏它的强大功能,尤其是在 Web 应用程序开发方面。尽管我对像 Kotlin 和 Clojure 这样的 JVM 替代语言也有涉猎,但我仍然对 Java 深有感情。很高兴看到 Java 团队致力于保持 Java 的活力,即便这意味着要对语言的核心部分进行重构。