
JEP 500,“让Final名副其实(Prepare to Make Final Mean Final)”已经在 JDK 26 中完成。该 JEP 旨在为 Java 生态系统做好准备,未来将不再允许通过深度反射(deep reflection)修改声明为 final 的字段,以前,这种操作通常是通过AccessibleObject类中定义的setAccessible()方法实现的。此举标志着一条弃用路径的开始,在未来的 JDK 版本中,默认行为将会变为抛出IllegalAccessException,从而彻底堵上 Java 封装模型中长期存在的一个漏洞。
长期以来,尽管final字段被视为不可变性的契约,但 Java 历史上一直允许通过反射破坏这一契约。这个最初为支持对象序列化而保留的漏洞,被 OpenJDK 团队称为累积的“完整性债务(integrity debt)”。随着时间的推移,依赖注入、序列化和 Mock 框架广泛依赖反射修来修改 final 字段,这迫使 JVM 的即时编译器(JIT)不得不做出保守性的假设,从而限制了依赖真正不可变性的优化(如常量折叠)。通过恢复final关键字更强的不可变性保障,平台将能启用更激进的优化,并为开发者和架构师提供更可靠的语义,尤其在并发程序中。
在 JDK 26 的新行为中,尝试通过深度反射修改final字段的代码仍会成功执行(以保持兼容性),但 JVM 会在每个模块首次发生此类操作时默认输出警告。
考虑如下的样例:
在运行期,这段代码将触发一个警告,指出 final 字段已被反射修改,如下所示:
该行为由--illegal-final-field-mutation选项控制,JDK 26 中它的默认值为warn。
为管理这一转变,JEP 500 引入了多种运行时模式,与近年来 Java 版本中其他访问控制机制的做法类似:
--illegal-final-field-mutation=warn(默认值)继续支持反射,但是会发出一个警告
--illegal-final-field-mutation=deny当尝试进行反射变更时,会抛出
IllegalAccessException而失败--illegal-final-field-mutation=allow允许变更且没有警告(不推荐)
开发者也可在启动时选择性启用反射修改 final 字段的功能,例如:
java --enable-final-field-mutation=ALL-UNNAMED ...
或针对特定模块启用,从而为目前无法更新的遗留库提供可控的逃生通道。
为了进一步帮助团队为未来更严格的强制执行做好准备,JDK 26 还集成了 JDK Flight Recorder(JFR)。新增的jdk.FinalFieldMutation事件会记录每次 final 字段被修改的情况,并附带完整堆栈跟踪。这使得团队能在“deny”模式成为默认值之前,全面审计整个依赖树中的违规行为。
通过这些变更,Java 正回归对final更严格、更贴近其最初设计意图的阐述,同时仍为遗留场景提供了清晰的迁移路径。
原文链接:
JEP 500: Java to Enforce Strict Final Field Immutability by Restricting Reflection







评论