探索Java 25的若干语言增强特性
尽管Java 26在今年3月中旬已经发布,但Java 25是目前最新的LTS(长期支持)版本,因此我选择优先关注它。无论Java 25的某些语言改进是否仍以预览功能的形式提供,本文都将简要概述其中的几个。主要目的是首先让开发人员意识到,Java正通过其API贡献者不断优化和演进;其次,是激发大家深入探索这些增强功能的好奇心和兴趣。
在JDK 25提出的众多功能中[资源1],本文简要探讨了以下五个语言增强点:视频10音频5音频6访问广告商网站前往页面
- JEP 512 – 紧凑源文件和实例main方法
- JEP 513 – 灵活构造函数体
- JEP 507 – 模式、
instanceof和switch中的原始类型 - JEP 506 – 作用域值
- JEP 502 – 稳定值
紧凑源文件和实例main方法 (JEP 512)
该功能最初在JDK 21中作为JEP 445“未命名类和实例main方法”提出,随后根据反馈在接下来的版本中逐步改进,并于JDK 25中最终确定。目标很明确:简化Java的入口点,方便初学者和小程序使用——减少样板代码和仪式感——同时与标准Java语言和工具链完全兼容。
假设我们想快速编写一个小程序,实现以下功能:
- 提示用户并循环读取输入
- 如果用户输入exit(不区分大小写),则打印"Goodbye!"并结束
- 否则,打印输入字符串的长度
代码直接放在包中的一个名为CompactSourceFile.java的文件中,内容如下:
简洁明了——没有类声明,只有目标简单的代码段。运行并给出几个提示后,输出符合预期:
几点值得注意:
- 不再需要显式的类声明
- 尽管不可见,编译器会隐式声明一个final类,且属于未命名包
- 传统的
public static void main(String[] args)被替换为一个更简单的实例方法,它明确作为程序入口点 - 程序入口点仍需命名为
main(),因为JVM会查找这样一个可启动的方法 - 所有字段和方法都属于隐式类,与常规情况一致
- 简单程序直接聚焦于其目的,无需额外细节
- 这是实验性的,也很直接。但如果演变成真正的应用程序,建议保留面向对象结构及所有已知的最佳实践
灵活构造函数体 (JEP 513)
在JDK 25之前,构造函数有一条明确规则:在super()或this()调用之前不能编写任何语句。为了提升表达力和可读性,JEP 513放宽了这一限制,而现有代码依然能编译和正常运行,并且对象的安全性100%得到保留。
在Java中,当构造一个对象实例时,会发生两个阶段:一个在前,一个在后;构造函数链的层次结构开始执行。在前一个阶段,内存被分配,实例字段被初始化;在后一个阶段,一旦this()和super()调用完成,对象的其余部分基本构造完成。这个过程主要出于安全考虑,确保继承的对象部分在子类相关代码运行之前完全初始化。Joshua Bloch在《Effective Java》中已经建议防止this引用“过早”逃逸。结果就是:对象在任何时刻都不会处于部分构造状态。
简单来说,从Java 25开始,现在允许在构造函数体内的this()或super()之前执行语句,同时内部不会在对对象核心安全构建方面做出任何妥协。
注意:
- 允许的语句——仅那些不依赖实例状态且保证安全的语句:
- 操作栈上声明的局部变量
- 构造函数参数验证
- 语法更加宽松,对象安全性得以保留
让我们看一个小例子,其中通过近似长度和轮子数量来简单建模一个Car,其中长度继承自超类Vehicle。
运行后,输出为——Car is about 5 meters long and has 4 wheels。首先观察到,Vehicle#length首先被四舍五入(因为存储为long值,第13行),然后传递给另一个构造函数。其次,在调用超类构造函数之前(第30行),对wheels进行了验证,然后赋值。
现在用记录(record)建模一个摩托车:
在Java 25之前,规范记录构造函数中允许参数验证(第2行),现在扩展到非规范构造函数(第12行),此外还允许this()调用。如果运行,moto1仅使用规范构造函数构造,而moto2同时使用两个构造函数,输出如下:
关于enum,考虑以下实验代码:
虽然像第一个构造函数中的验证在Java 25之前就允许,但现在也允许在调用this()之前进行额外操作。
总之,在class、record或enum级别,构造函数的编写方式得到了净化和改进,同时对象安全性依然得到完全保留,没有任何妥协。
模式、instanceof和switch中的原始类型 (JEP 507)
通常,模式匹配是一种语言机制,它将几个步骤组合成一个功能,便于测试某个值。重点在于检查的内容,而不一定是检查的手段。除了在instanceof和switch结构中使用模式匹配之外,Java 25还允许将其与原始类型一起使用——byte、short、int、long、float、double、char、boolean现在都纳入此模型。引用类型的限制现在被扩展,使该功能更加统一和直观,因为可应用的限制显著减少。
考虑以下示例:
如果运行,会产生以下输出:
注意:
describe()允许轻松地将一个Number描述为最适合它的最紧凑类型(第19行)- 一个
Number引用现在可以直接模式匹配到原始类型(第20行) - 该功能支持安全、无损的缩小检查,无需手动转换或范围检查
深入探索,我个人认为这个功能有趣的地方在于深层嵌套模式。下面的例子允许内省对象并直接匹配内容:
如果运行,结果很明显,但代码干净、简洁且极具表现力:
总之,从Java 25开始,就模式匹配功能的当前状态而言,代码整体上变得更干净、更安全。
作用域值 (JEP 506)
随着Project Loom引入虚拟线程,这为另一个增强功能留出了空间——在更结构化、可预测且更安全的方式下,在线程之间和跨线程传递不可变上下文。ScopedValues是Java 25中的一个最终确定功能,允许在精确的执行作用域边界内实现这一点。
为了更好地理解它们,我们来看下面的简单例子:
共享的User的存储位置首先被创建为USER。在执行过程中传递的上下文(而不是作为所调用方法的参数)是User实例。它可被视为“当前”用户。一旦实例被绑定(第23行),其作用域在main()方法中明确定义,并贯穿整个执行过程——传递给handle(),再进一步传递给handleFurther()。访问是只读的;不能更改。如果在执行流程中重新设置,如在handleFurther()中,会创建一个新的(嵌套)子作用域,一旦该子作用域结束,之前的外部作用域将继续。
如果运行,代码会生成以下输出,这更清楚地说明了上述内容:
如果在作用域外调用handle()(第30行)并重新运行代码,到达该点时会抛出一个明确的异常——Exception in thread "main" java.util.NoSuchElementException: ScopedValue not bound。
关键点:
where(...).run(...)—— 为lambda的执行期间绑定值,然后自动解除绑定——无需手动清理。- 作用域内不可变 —— 一旦绑定,无法更改(但可以在嵌套作用域中重新绑定)。
- 虚拟线程下开销小 —— 无需复制,只是一个引用。
- 易于推理 —— 值始终是当前作用域顶部绑定的那个。
- 是
ThreadLocal的良好替代品,后者生命周期无限、可变且难以推理,因为其值可以在调用栈的任何位置更改。 - 与结构化并发(JEP 505)完美配合 —— 子任务自动共享父级的作用域值而无需复制。
总之,作用域变量极大地促进了并发的清晰性和安全性,并有助于防止诸如内存泄漏或陈旧数据泄露等问题。
稳定值 (JEP 502)
我认为这个增强功能强制实现了有效的不变性——无论是在实例级别还是对象级别。如果在Java 25之前我们创建了一个实例,将其声明为final,初始化它,并记录它应保持不变,但实际情况有时不同,因为实例的某些“内容”仍然可变。StableValue功能允许通过所有手段构造不可变实例,以便一旦初始化,对象内容也保证保持不变。
StableValues是一种JVM增强功能,提供了一种实现线程安全和深度不变性的方法,是结合锁、同步、volatile变量和Atomic引用的替代方案。其行为是天生线程安全的,这是由JVM对StableValues的内部处理确保的。
我们来检查以下代码:
注意:
User由两个属性简单描述——name在构造时提供,而id表示内部唯一标识符。id声明为StableValue,并在读取值时进行惰性初始化(在并发上下文中,由第一个执行该操作的线程初始化)- 一旦初始化,该值就是深度不可变的;无法更改,并保持如此直到对象被销毁
如果运行,输出如下:
很明显,id在需要时设置,并且之后每次读取时其值都保持不变。
最后一点值得注意,关于User#id属性——作为StableValue,它自动是线程安全且无锁的。为了演示这一点,让我们运行下一段代码:
任务1、2和3被创建并设置为读取预先创建的用户id,然后并行执行。下面的输出显示,在这次特定运行中,任务3设置了id,然后任务1和2使用了相同的值。
StableValue还附带了一些更高级的辅助方法(function()、intFunction()、list()、map()、supplier()),每个都在各种场景中非常有用和适用。下面是Singleton模式如何实现的示例:
目标是拥有一个UserService的单一实例,用于通过指定方法注册用户。如果运行,输出如下,清楚地显示构造函数只被调用了一次:
总之,StableValue增强功能确保了在JVM级别强制实现不变性——一旦值被设置,它就是稳定的,并对所有线程可见。
结论
本文简要介绍了Java 25的几个语言增强功能,希望这些直接的示例能为深入探索这些功能提供一个起点。无论你是否已经迁移到最新的LTS,无论你是否开始探索最新的添加和改进,我都认为这无论如何都值得一做。
在JavaOne 2026的一次开幕主题演讲中,我注意到了这样一句话:“Java 无处不在,AI 需要它。”我完全同意。在一个显然每个人都专注于“加速推理”的世界里,让我们对未来保持乐观,继续构建和巩固我们的Java基础——通过探索新的添加、保持最新,并逐步将它们应用到我们的个人和专业项目中去。