reflectionの間違った使い方(final涙目)
reflectionを使うと以下のことが出来る
- final指定のフィールドを変更できる
- private指定のフィールドを参照・変更できる
※あえてフィールドと使ったのは対象クラスのメンバ変数を変更することができないため
正確には定数だけど、面倒くさいので以下メンバ変数に統一
作成手順
reflectionで参照するクラスの作成
リフレクションアクセスするメンバ変数とgetterの作成
- public変数:PUB_STR1
- private変数:PRI_STR1
- メソッド:PRI_STR1のgetter
class RefTarget { public final String PUB_STR1 = "pub_str1"; private final String PRI_STR1 = "pri_str1"; public final String getPRI_STR1() { return PRI_STR1; } }
参照する側のクラスの作成
参照ターゲットのインスタンスを生成
RefTarget target = new RefTarget();
ターゲットクラスのリフレクション・アクセス用のClassインスタンスの作成
Class<? extends RefTarget> cls = target.getClass();
public finalのテスト
ターゲットクラスのpublicフィールド用変数の作成
setAccessible(true)でアクセス可能にする。
Field target_PUB_STR1 = cls.getField("PUB_STR1"); target_PUB_STR1.setAccessible(true);
変更前の表示処理
System.out.println("--public--"); System.out.println("target field: " + target_PUB_STR1); System.out.println("befor: " + (String)target_PUB_STR1.get(target));
final指定のフィールドを変更する
target_PUB_STR1.set(target, "change_pub_str1");
変更確認
変更されているのを確認
System.out.println("aflter: " + (String)target_PUB_STR1.get(target));
インスタンスレベルで変更されているのか確認
System.out.println("instance: " + target.PUB_STR1);
実行結果
--public-- target field: public final java.lang.String RefTarget.PUB_STR1 befor: pub_str1 aflter: change_pub_str1 instance: pub_str1
変更されていない。
参照先は違う様子。
インスタンス内のフィールドとreflectionフィールドはどうやら参照アドレスが違うみたい。
良く分からないけど、Fieldオブジェクトは参照先のコピーを作成するのかな?
そもそもfinal指定の変更が出来る時点で間違っていると思う。
private finalのテスト
ターゲットクラスのprivateフィールド用変数の作成
setAccessible(true)でアクセス可能にする。
publicと違うのはgetDeclaredFieldメソッドで取得すると、privateなメンバ変数もアクセス可能になる。
※この時点で変
Field target_PRI_STR1 = cls.getDeclaredField("PRI_STR1"); target_PRI_STR1.setAccessible(true);
変更とか取得とかはpublicとおなじ。
System.out.println("--private--"); System.out.println("target field: " + target_PRI_STR1); System.out.println("befor: " + (String)target_PRI_STR1.get(target)); //change target_PRI_STR1.set(target, "change_pri_str1"); System.out.println("after: " + (String)target_PRI_STR1.get(target)); System.out.println("instance: " + target.getPRI_STR1());
実行結果
結果もpublicと同じ
--private-- target field: private final java.lang.String RefTarget.PRI_STR1 befor: pri_str1 after: change_pri_str1 instance: pri_str1
ソース
import java.lang.reflect.Field; class RefTarget { public final String PUB_STR1 = "pub_str1"; @SuppressWarnings("unused") private final String PRI_STR1 = "pri_str1"; } public class RefTest { public static void main(String[] args) throws Exception { RefTarget target = new RefTarget(); Class<? extends RefTarget> cls = target.getClass(); //public test Field target_PUB_STR1 = cls.getField("PUB_STR1"); target_PUB_STR1.setAccessible(true); System.out.println("--public--"); System.out.println("target field: " + target_PUB_STR1); System.out.println("befor: " + (String)target_PUB_STR1.get(target)); //change target_PUB_STR1.set(target, "change_pub_str1"); System.out.println("aflter: " + (String)target_PUB_STR1.get(target)); System.out.println("instance: " + target.PUB_STR1); System.out.println(); //private test Field target_PRI_STR1 = cls.getDeclaredField("PRI_STR1"); target_PRI_STR1.setAccessible(true); System.out.println("--private--"); System.out.println("target field: " + target_PRI_STR1); System.out.println("befor: " + (String)target_PRI_STR1.get(target)); //change target_PRI_STR1.set(target, "change_pri_str1"); System.out.println("after: " + (String)target_PRI_STR1.get(target)); System.out.println("instance: " + target.getPRI_STR1()); } }
実行結果
--public-- target field: public final java.lang.String RefTarget.PUB_STR1 befor: pub_str1 aflter: change_pub_str1 instance: pub_str1 --private-- target field: private final java.lang.String RefTarget.PRI_STR1 befor: pri_str1 after: change_pri_str1 instance: pri_str1
staticでやってみた その1
public変数にfinal指定のまま、static指定にしてみる
public static final String PUB_STR1 = "pub_str1";
アクセスする
staticフィールドのアクセスは引数をnullで指定する
System.out.println("befor: " + (String)target_PUB_STR1.get(null)); target_PUB_STR1.set(null, "change_pub_str1");
実行結果
--public-- target field: public static final java.lang.String RefTarget.PUB_STR1 befor: pub_str1 Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.String field RefTarget.PUB_STR1 to java.lang.String at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:55) at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:59) at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:59) at java.lang.reflect.Field.set(Field.java:657) at RefTest.main(RefTest.java:24)
setメソッドで実行エラーが起きる。
どうやらstaticはリフレクションを使った変更は出来ない様子
staticでやってみた その2
public変数にfinal指定を除いて、static指定にしてみる
public static String PUB_STR1 = "pub_str1";
アクセス
System.out.println("befor: " + (String)target_PUB_STR1.get(null)); target_PUB_STR1.set(null, "change_pub_str1"); System.out.println("aflter: " + (String)target_PUB_STR1.get(target)); System.out.println("instance: " + RefTarget.PUB_STR1);
実行結果
--public-- target field: public static java.lang.String RefTarget.PUB_STR1 befor: pub_str1 aflter: change_pub_str1 instance: change_pub_str1
実行エラーは起きない。
今回は直接アクセスしても変更されているのが分かる。まぁ当たり前か。
staticでやってみた その3
private変数にfinal指定のまま、static指定にしてみる
private static final String PRI_STR1 = "pri_str1"; public static String getPRI_STR1() { return PRI_STR1; }
アクセス
System.out.println("befor: " + (String)target_PRI_STR1.get(null)); target_PRI_STR1.set(null, "change_pri_str1");
実行結果
--private-- target field: private static final java.lang.String RefTarget.PRI_STR1 befor: pri_str1 Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.String field RefTarget.PRI_STR1 to java.lang.String at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:55) at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:59) at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:59) at java.lang.reflect.Field.set(Field.java:657) at RefTest.main(RefTest.java:40)
publicと同じでsetメソッドで実行エラーになる
staticでやってみた その4
private変数にfinal指定を除いて、static指定にしてみる
private static String PRI_STR1 = "pri_str1";
アクセス
System.out.println("befor: " + (String)target_PRI_STR1.get(null)); target_PRI_STR1.set(null, "change_pri_str1"); System.out.println("after: " + (String)target_PRI_STR1.get(null)); System.out.println("instance: " + RefTarget.getPRI_STR1());
実行結果
--private-- target field: private static java.lang.String RefTarget.PRI_STR1 befor: pri_str1 after: change_pri_str1 instance: change_pri_str1
publicと同じ。
結論
- final指定のメンバ変数はフィールドとして変更可能、ただしインスタンスレベルの変更はできない。
- private finalのメンバ変数も同様
- static finalはpublicでもprivateでも参照はできるが変更はできない。