Javaのプライベートメソッドを呼んでみる

## はじめに

使い方によってはイリーガルなjavaのリフレクション。
プライベートメソッドや、Androidによく使われている`@hide`アノテーションがコールできてしまう。

そんなに使う機会はないけれども、クラスのメソッドなどを文字列としてリスト取得できたりするので、独自のRPCなどを作るときなど使ったりした。どうしてもAndroidのhide関数を使いたかったりした時に利用させてもらった。

シンプルなコードでメモ。([github](https://github.com/caad1229/java_reflection))

## Javaプロジェクトの準備

今回は、`maven`でjavaプロジェクトを作る。(まめに使って忘れないように。。)

```bash
$ mvn archetype:generate \
       -DgroupId=com.caad1229 \
       -Dpackage=com.caad1229.testReflection \
       -DartifactId=testReflection \
       -DarchetypeArtifactId=maven-archetype-quickstart \
       -DinteractiveMode=false
```

`mvn`コマンドでjavaを実行させるためプラグインを追加

```xml
$ 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>
```


## 実装

### プライベートメソッドを持つクラスを用意する。

型別に、そのまま値を返すプライベート関数を持つクラスを作成。

```java
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`が発生する。

```java
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`で実行する。メッセージから、プライベートメソッドがコールできた。

```bash
$ 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`は、融通を聞いてくれるありがたい言語でもあります。