日々常々

ふつうのプログラマがあたりまえにしたいこと。

リフレクションでprivateをテストしてみる

発端


[twitter:@meganii] さんのつぶやきが発端。不可視クラスってことは…privateクラスと無名クラス?これらをリフレクションで云々したこと無かったので、やってみました。

対象クラス

package test.reflect;

public class Target {

	public String publicMethod() {

		new Object() {
			String test() {
				return "AnonymousClassTest";
			}
		};

		return "Public";
	}

	private String privateMethod() {
		return "Private";
	}


	private static class PrivateStatic {
		String test() {
			return "PrivateStaticClassTest";
		}
	}

	private class Private {
		String test() {
			return "PrivateClassTest";
		}
	}
}

それぞれのメソッドを単独で実行するテストコード

package test.reflect;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import org.junit.Test;

public class TargetTest {

	@Test
	public void publicMethod() throws Exception {
		Target target = new Target();
		String actual = target.publicMethod();
		assertThat(actual, is("Public"));
	}

	@Test
	public void privateMethod() throws Exception {
		Target target = new Target();

		Method m = Target.class.getDeclaredMethod("privateMethod");
		m.setAccessible(true);

		String actual = (String) m.invoke(target);
		assertThat(actual, is("Private"));
	}

	@Test
	public void privateStaticClass() throws Exception {
		ClassLoader loader = ClassLoader.getSystemClassLoader();
		Class clz = loader.loadClass("test.reflect.Target$PrivateStatic");

		Constructor c = clz.getDeclaredConstructor();
		c.setAccessible(true);
		Object o = c.newInstance();

		Method m = clz.getDeclaredMethod("test");
		m.setAccessible(true);

		String actual = (String) m.invoke(o);
		assertThat(actual, is("PrivateStaticClassTest"));
	}

	@Test
	public void privateClass() throws Exception {
		ClassLoader loader = ClassLoader.getSystemClassLoader();
		Class clz = loader.loadClass("test.reflect.Target$Private");

		Constructor c = clz.getDeclaredConstructor(Target.class);
		c.setAccessible(true);
		Object o = c.newInstance(new Target());

		Method m = clz.getDeclaredMethod("test");
		m.setAccessible(true);

		String actual = (String) m.invoke(o);
		assertThat(actual, is("PrivateClassTest"));
	}

	@Test
	public void anonymousClass() throws Exception {
		ClassLoader loader = ClassLoader.getSystemClassLoader();
		Class clz = loader.loadClass("test.reflect.Target$1");

		Constructor c = clz.getDeclaredConstructor(Target.class);
		c.setAccessible(true);
		Object o = c.newInstance(new Target());

		Method m = clz.getDeclaredMethod("test");
		m.setAccessible(true);

		String actual = (String) m.invoke(o);
		assertThat(actual, is("AnonymousClassTest"));
	}
}

解説は…コードが全てでいい気がするのでとりあえず書きません。(追記するかも?)

内部クラスも無名クラスもリフレクションで呼ぶのはできるといえばできます。
でもクラス名、メソッド名ともにリテラルになっちゃうのでIDEのサポートも得られないため、リファクタリングしづらくなります。開発の初期段階はともかく最終的にはpublicな属性を相手にしたテストで賄ってしまって削除しちゃったほうがいいと思います。

無名クラスは出来る気がしたのでやってみたら出来たって感じだったりします。とりあえず不可視クラスを相手にすると面倒な事はわかりました。