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でも参照はできるが変更はできない。