Android、Java、Web系、Linux、マラソン等の備忘録

2013/01/09

Javaでprivateなfieldやmethodにアクセスする

0 件のコメント
JUnitでテストしてると、privateなフィールドにアクセスして、値を参照したりセットしたりしたくなるわけですが、よく使うのでメモしておきます。

例えば、次のような対象のクラスがあるとします。
    public class ParentClass {
        private String hoge = "ParentClass!!";

        public void dispMsg() {
            System.out.println("dispMsg:" + hoge);
        }

        private void privateDispMsg(String msg) {
            System.out.println("dispMsg:" + msg);
        }
    }

privateなフィールドを取得

hogeの値を取得したい場合、下記のようなメソッドを用意しておけば、
    public static Object getPrivateField(Object target_object, String field_name) throws Exception{
        Class c = target_object.getClass();
        Field fld = c.getDeclaredField(field_name);
        fld.setAccessible(true);

        return fld.get(target_object);
    }
このメソッドを使って、以下のようにアクセスできます。
        ParentClass parent = new ParentClass();
        System.out.println("private field:" + (String)getPrivateField(parent, "hoge"));
実行結果:「private field:ParentClass!!」

privateなフィールドを書き換え

値を更新したい場合は、下記のようなメソッドを用意しておけば、
    public static void setPrivateField(Object target_object, String field_name, Object value) throws Exception{
        Class c = target_object.getClass();
        Field fld = c.getDeclaredField(field_name);
        fld.setAccessible(true);
        fld.set(target_object, value);
    }
以下のように記述する事で、プライベートフィールド書き換えることができます。
        ParentClass parent = new ParentClass();
        setPrivateField(parent, "hoge", "from test method");
        parent.dispMsg();
実行結果:「dispMsg:from test method」

プライベートなメソッドを実行

外部からprivateDispMsg()を実行したい場合は以下のようなメソッドを用意しておけば、
    public static Object doPrivateMethod(Object target_object, String field_name, Class[] classArray, Object[] args) throws Exception{

        Class c = target_object.getClass();
        Method method = c.getDeclaredMethod(field_name, classArray);
        method.setAccessible(true);

        return method.invoke(target_object, args);
    }
以下のように書けば実行できます。classArrayとparamで引数を指定しています。
        ParentClass parent = new ParentClass();
        Class[] classArray = {String.class};
        String[] param = {"hogehoge"};
        doPrivateMethod(parent, "privateDispMsg", classArray, param);
実行結果:「dispMsg:hogehoge」
また、引数無しの場合は以下のように、
    public static Object doPrivateMethod(Object target_object, String field_name) throws Exception{
        Class c = target_object.getClass();
        Method method = c.getDeclaredMethod(field_name);
        method.setAccessible(true);

        return method.invoke(target_object, null);
    }

対象クラスを継承して使う場合

例えばこのように、
        ParentClass child = new ParentClass() {
            @Override
            public void dispMsg() {
                System.out.println("override!!");
            }
        };
        System.out.println("private field:" + (String)getPrivateField(child, "hoge"));
テストだとありがちだと思うんですけど、これをこのまま実行すると、エラーになります↓
java.lang.NoSuchFieldException: hoge
 at java.lang.Class.getDeclaredField(Unknown Source)
 at net.takaiwa.test.util.PrivateFieldTest.getPrivateField(PrivateFieldTest.java:69)
 at net.takaiwa.test.util.PrivateFieldTest.testSetPrivateField(PrivateFieldTest.java:37)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at junit.framework.TestCase.runTest(TestCase.java:164)
 at junit.framework.TestCase.runBare(TestCase.java:130)
 at junit.framework.TestResult$1.protect(TestResult.java:106)
 at junit.framework.TestResult.runProtected(TestResult.java:124)
 at junit.framework.TestResult.run(TestResult.java:109)
 at junit.framework.TestCase.run(TestCase.java:120)
 at junit.framework.TestSuite.runTest(TestSuite.java:230)
 at junit.framework.TestSuite.run(TestSuite.java:225)
 at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
以下のように修正してやるとアクセスできるようになります。どう取得してるかはparentとかchildとか名前でわかるかと思います。
        ParentClass child = new ParentClass() {
            @Override
            public void dispMsg() {
                System.out.println("override!!");
            }
        };
        Field parent_fld = ParentClass.class.getDeclaredField("hoge");
        parent_fld.setAccessible(true);
        System.out.println("private field:" + (String)parent_fld.get(child));


0 件のコメント :

コメントを投稿