JDK26 JEP 500: finalを「本当にfinal」にするための準備


はじめに

従来の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フィールドを書き換えているコードの正確な場所を特定できます。