はじめに
従来のJavaプラットフォームでは、java.lang.reflect.Field クラスの setAccessible(true)と set(...) メソッドによるディープリフレクションにより、finalフィールドであっても強制的に書き換えることができます(これは、オブジェクトをデシリアライズする際にフィールドを初期化する必要があるシリアライゼーション・ライブラリのために導入されましたが、この機能が制約なく提供されていました)。
そのため、JVM は finalフィールドが本当に不変であると信頼できず、最適化)の妨げになったり、プログラムの安全性を損なう可能性があります。
将来的に、リフレクションによる final フィールドの書き換えは、デフォルトで禁止される予定です。
JEP 500 では、この準備として、final フィールドの書き換操作に対してランタイム警告を発行します。
500:Prepare to Make Final Mean Final
finalフィールドは、一度初期化されたら変更されないこと(不変性)を意図しています。 これは、プログラムの正当性を担保したり、JVMが「定数畳み込み」のような最適化を行ったりするために非常に重要です。
JDK 26 では、リフレクションによってfinalフィールドを不正に書き換えようとすると、デフォルトで警告が標準エラーに出力されます。
将来のリリースでは、この動作が「警告 (warn)」から「禁止 (deny)」に変更され、警告の代わりにIllegalAccessExceptionがスローされる予定です。
finalフィールドの書き換えに依存しているライブラリなどを使用する場合、アプリケーション開発者は起動時に以下のオプションを指定して、書き換えを明示的に許可する必要があります。
クラスパス上のすべてのコードに許可
--enable-final-field-mutation=ALL-UNNAMED
特定のモジュールに許可
--enable-final-field-mutation=モジュール名
動作モードの指定
--illegal-final-field-mutation で動作モードを指定できます。
--illegal-final-field-mutation=debug
warn:書き換えを許可し、モジュールごとに最初の1回だけ(ランタイム)警告する(JDK 26のデフォルト)deny:書き換えを禁止し、IllegalAccessExceptionをスローする(将来のデフォルト)debug:不正な書き換えごとに、警告とスタックトレースを出力する
debug モードで実行するか、JDK Flight Recorder で jdk.FinalFieldMutation イベントを記録することで、finalフィールドを書き換えているコードの正確な場所を特定できます。