はじめに
使い方によってはイリーガルなjavaのリフレクション。
プライベートメソッドや、Androidによく使われている@hide
アノテーションがコールできてしまう。
そんなに使う機会はないけれども、クラスのメソッドなどを文字列としてリスト取得できたりするので、独自のRPCなどを作るときなど使ったりした。どうしてもAndroidのhide関数を使いたかったりした時に利用させてもらった。
シンプルなコードでメモ。(github)
Javaプロジェクトの準備
今回は、maven
でjavaプロジェクトを作る。(まめに使って忘れないように。。)
$ mvn archetype:generate \
-DgroupId=com.caad1229 \
-Dpackage=com.caad1229.testReflection \
-DartifactId=testReflection \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
mvn
コマンドでjavaを実行させるためプラグインを追加
$ vim ./testReflection/pom.xml
: 省略
<!-- 追加 start -->
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<!-- 実行するパッケージ -->
<mainClass>com.caad1229.testReflection.App</mainClass>
</configuration>
</plugin>
</plugins>
</build>
<!-- 追加 end -->
</project>
実装
プライベートメソッドを持つクラスを用意する。
型別に、そのまま値を返すプライベート関数を持つクラスを作成。
public class Refref {
private void funcVoid () { }
private String funcString (String str) { return str; }
private boolean funcBoolean (boolean bool) { return bool; }
private int funcInt (int i) { return i; }
}
リフレクションを使ってプライベートメソッドを呼ぶ
すべてのメソッドを実行する。プライベートを含むすべてのメソッドが欲しい場合は、getDeclaredMethods
で取得する。そして実行する前にsetAccessible(true)
でアクセス許可しなければIllegalAccessException
が発生する。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class App {
public static void main( String[] args ) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Refref obj = new Refref();
// methodオブジェクトを全て取得
Method methods[] = obj.getClass().getDeclaredMethods();
// 全てのメソッドを実行してみる
for (int i=0; i<methods.length; i++) {
Method method = methods[i];
// 属性をアクセス可能に変更
method.setAccessible(true);
String n = method.getName();
if (n.equals("funcVoid")) {
method.invoke(obj, null);
System.out.println( "method : " + n );
} else if (n.equals("funcString")) {
String ret = (String) method.invoke(obj, new Object[]{"test"});
System.out.println( "method : " + n + " / ret : " + ret);
} else if (n.equals("funcBoolean")) {
Boolean ret = (Boolean)method.invoke(obj, new Object[]{new Boolean(true)});
System.out.println( "method : " + n + " / ret : " + ret);
} else if (n.equals("funcInt")) {
Integer ret = (Integer)method.invoke(obj, new Object[]{new Integer(10)});
System.out.println( "method : " + n + " / ret : " + ret);
}
}
}
}
実行
ビルドしてexec:java
で実行する。メッセージから、プライベートメソッドがコールできた。
$ mvn clean compile exec:java
:
: 省略
method : funcVoid
method : funcString / ret : test
method : funcBoolean / ret : true
method : funcInt / ret : 10
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.701s
[INFO] Finished at: Fri Aug 14 14:41:45 JST 2015
[INFO] Final Memory: 11M/981M
[INFO] ------------------------------------------------------------------------
おしまい
プライベートメソッドやhideアノテーションは、外部から使われるべきではない意図があります。使う側としてはそれに従うまでですし、自分が実装した場合は、パブリックなどに変更すべきです。
無理やり使うことは、実装者の意図に反しているわけで、リスクを伴います。勝手に仕様が変わって動かなくなっても文句は言えないですしね。
しかし、どうしても!という時、こういうことができるjava
は、融通を聞いてくれるありがたい言語でもあります。