前提
- Java 8
- 自分はJava初心者
コメント欄にてこういうもっといい方法があるよ、などのご報告お待ちしてます。
privateなメソッドのテストはつらい
/*
例えばHogeというクラスに
int foo(int i) {
return i+i;
}
という関数があったとする
*/
Hoge hoge = new Hoge();
Method m = Hoge.class.getDeclaredMethod("foo", int.class);
m.setAccessible(true);
int ret = (int)m.invoke(hoge, 10); // -> ret == 20
でもこれだけなら各ライブラリのお世話になるなどすれば大した事がない(気がする)。
privateなメソッドの引数がprivateなinterfaceだったらもっとつらい
こちらのページが近い事をしていたので大変参考になった。→技術見聞録 - 外部提供のInterfaceをインスタンス化する
以下のようなクラスがある事にする。
(今回の例のインタフェースは関数型インタフェース)
public class Hoge {
// クラス内からはこのようにアクセス可能
public int boo(int i) {
return foo(i, (int i) -> i+i);
}
private int foo(int i, InnerInterface innerInterfaceInstance) {
return innerInterfaceInstance.apply(i);
}
@FunctionalInterface // なくてもいいけど
private interface InnerInterface {
int apply(int i);
}
}
もしインナークラスなら、getDeclaredConstructorからのnewInstanceとかやっちゃうことが一応可能なのですが、インナーインタフェース(?)だと難しい。
これをテストするコードが以下。
これに至るまで失敗が無限にありましたが注意点は後述。
@Test
public void testFoo() throws Exception {
Hoge hoge = new Hoge();
// Interface内部の処理を移譲されるcallback
class InterfaceHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
return getClass().getMethod(name, method.getParameterTypes()).invoke(this, args);
public int apply(int i) {
// apply()にしてほしい内容を書く
// 例:
return i * i;
}
}
// パラメータの型の指定が大変なのでforループで名前から探す
Method fooDummy = null;
for (Method m : Hoge.class.getDeclaredMethods()) {
if (m.getName().equals("foo")) {
fooDummy = m;
}
}
// 黒魔術的設定
Class<?> interfaceClass = foo.getParameterTypes()[1]; // 2個目の引数
ClassLoader loader = interfaceClass.getClassLoader();
Class<?>[] interfaces = new Class<?>[] {interfaceClass};
InvocationHandler handler = new InterfaceHandler();
Object proxyInstance = Proxy.newProxyInstance(loader, interfaces, handler);
fooDummy.setAccessible(true);
// 実行!
int ret = (int)fooDummy.invoke(hoge, 10, proxyInstance);
assertEquals(ret, 100) // -> passed
}
java.lang.reflect.InvocationHanlder
java.lang.reflect.Proxy
の2つを利用して、interfaceの関数をこっちで指定してやる感じですね。
注意点
- インタフェースの型が必要になるのでforループ使って名前でmethodを取り出す。(もっといい方法もあるかもしれない)
- ちなみにHoge#boo()みたいにラムダ渡すと怒られます。invokeの引数がObjectを要求しているからでしょうか?
結論
package privateにしような
どうやら、AOP(アスペクト指向プログラミング)の実現のために使われるるらしい(下記参照)