[2014-03-25追記]
インスタンスフィールドなら、finalであってもリフレクションで変更できました。
以下の話は、static finalなフィールド限定です。
昔はどうだったか知りませんが、少なくともJava7ではリフレクションを駆使してもstatic finalフィールドの値を書き換えることができません。
例えば、
public class Foo { static class Sample { static final List list = Arrays.asList("foo"); } public static void main(final String... args) throws Exception { final Field f = Sample.class.getDeclaredField("list"); f.set(null, Arrays.asList("bar")); System.out.println(f.get(null)); } }
のようなコードは、
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.util.List field foo.Foo$Sample.list to java.util.Arrays$ArrayList at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException( UnsafeFieldAccessorImpl.java:73) at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException( UnsafeFieldAccessorImpl.java:77) at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set( UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77) at java.lang.reflect.Field.set(Field.java:741) at foo.Foo.main(Foo.java:26)
と怒られるだけ。
そもそも何でstatic finalフィールドを書き換えたいかと言うと、
とあるOSSライブラリのパフォーマンスチューニングをしていて、
どうしても書き換えたいprivate static finalなフィールドが出てきたため。
ソースまるごとコピーして書き換えるというのは余りやりたくなかったので、
どうにか他の手段がないか探しました。
結論:Unsafeを使う
出オチ感満点で恐縮ですが、他に良い手段がなかったのでUnsafeを使うことにしました。
「Unsafeって何?」な方はどうぞググって頂ければと思いますが、
要するにヒープメモリを直接いじくり回すネイティブメソッド群です。
もちろん、Javassistでバイトコードを動的に書き換える方法でも良いのですが、
アプリケーション起動時に気をつけなければならないことが増える(特に、クラスローディングの挙動)ので止めました。
Unsafeはヒープを直接操作します。従って使い方を間違えると即、VMクラッシュになります。
VMクラッシュ時のリカバリ用スクリプトの試験に向いているかもw
実装
さて、Unsafeを直接使うとEclipse様が激おこプンプン丸なので
ファサード経由で使うようにしました。まあこんな感じ。
/* PUBLIC DOMAIN */ final class FinalFieldSetter { private static final FinalFieldSetter INSTANCE; static { try { INSTANCE = new FinalFieldSetter(); } catch (final ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } } private final Object unsafeObj; private final Method putObjectMethod; private final Method objectFieldOffsetMethod; private final Method staticFieldOffsetMethod; private final Method staticFieldBaseMethod; private FinalFieldSetter() throws ReflectiveOperationException { final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); final Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); this.unsafeObj = unsafeField.get(null); this.putObjectMethod = unsafeClass.getMethod("putObject", Object.class, long.class, Object.class); this.objectFieldOffsetMethod = unsafeClass.getMethod("objectFieldOffset", Field.class); this.staticFieldOffsetMethod = unsafeClass.getMethod("staticFieldOffset", Field.class); this.staticFieldBaseMethod = unsafeClass.getMethod("staticFieldBase", Field.class); } public static FinalFieldSetter getInstance() { return INSTANCE; } public void set(final Object o, final Field field, final Object value) throws Exception { final Object fieldBase = o; final long fieldOffset = (long) this.objectFieldOffsetMethod.invoke( this.unsafeObj, field); this.putObjectMethod.invoke(this.unsafeObj, fieldBase, fieldOffset, value); } public void setStatic(final Field field, final Object value) throws Exception { final Object fieldBase = this.staticFieldBaseMethod.invoke(this.unsafeObj, field); final long fieldOffset = (long) this.staticFieldOffsetMethod.invoke( this.unsafeObj, field); this.putObjectMethod.invoke(this.unsafeObj, fieldBase, fieldOffset, value); } }
これを使うと、無事
public class Foo { static class Sample { static final List list = Arrays.asList("foo"); } public static void main(final String... args) throws Exception { System.out.println(Sample.list); // 注:staticフィールドを初期化するために必要 final Field f = Sample.class.getDeclaredField("list"); FinalFieldSetter.getInstance().setStatic(f, Arrays.asList("bar")); System.out.println(Sample.list); } }
がエラー無く動くわけです。めでたしめでたし。
Unsafeクラスの使い方は解説するのが面倒なので、各自Javadocを参照して下さい。手抜き。
まあ思いっきりメモリ操作風なAPIです。ほぼC。
Javadocはどこにある?
Unsafeは非公開・非推奨APIなのでOracleからの公式なJavadocはリリースされていませんが、例えば
http://www.docjar.com/docs/api/sun/misc/Unsafe.html
あたりにブツがあります。OracleJDKではなくてOpenJDKのものですが、まあ同じです。