【Java】リフレクション privateメソッド色々~wrong number ofを添えて~ | 人生のエラー集

人生のエラー集

バグ出し率トップを誇るへっぽこプログラマーが発生させたエラーをメモのために残します。
同じエラーで悩む人の解決策になりますように…

趣味のことも書いたりしますが。

この前、JUnit使ってる時にハマってしまったお話です。
結局は使い方を気を付けようねって話なんですけど、あの時は本当にうみが全然わかんない、そんな状態でした。
同じミスをする人がいなくなるように、このメモを残します。

Javaのリフレクションをよく使うのは主にJUnitとかですかね。あとは、フレームワークとか。
privateメソッドを呼び出すときに使います。

// private 引数なし(非static)の例
// ReflectionTestは実行したいメソッドが実装されているクラス
// 第一引数は実行したいメソッド名。第二引数は空のClassの配列。
Method method = ReflectionTest.class.getDeclaredMethod("privateMethod", new Class[]{});
// privateアクセスにはこれが必要。呪文。
method.
setAccessible(true);
// 実行可能。invokeの第一引数は実行したいメソッドが実装されているクラスのインスタンス
// 第二引数はObjectの配列。特に気にしない。
// privateMethod戻りはStringなのでキャストする
System.out.println((String)method.invoke(ReflectionTest.class.newInstance(), new Object[]{}));
// 実行可能。引数なしのメソッドに対してはこういう書き方もできる
System.out.println((String)method.invoke(ReflectionTest.class.newInstance()));

↑はReflectionTestクラスに引数なしで戻りがStringのprivateMethodを実装すれば実行できます。
Classの配列だったりObjectの配列が登場してきて、なんだこれは?状態かもしれませんが、次の引数ありパターンを見たら理解できると思います。



// ◆private 引数あり
// Classの型にはprivateMethodの引数の型を順番に設定する
Method privataMethodPlusString = ReflectionManager.class.getDeclaredMethod("privateMethod", new Class[]{String.class, int.class});
privataMethodPlusString.setAccessible(true);
// 引数に渡す値
String value = "value";
int num = 1;
// 第二引数にするObjectの配列。privateMethodの引数の順に配列に設定する
Object[] objects = new Object[]{value, num};
// 実行可能。これがinvokeの定型
System.out.println((int)privataMethodPlusString.invoke(ReflectionManager.class.newInstance(), objects));
// 実行可能。ただ、オススメしない。後述するエラーパターン参照
System.out.println((int)privataMethodPlusString.invoke(ReflectionManager.class.newInstance(), value, num));
// java.lang.IllegalArgumentException: wrong number of arguments 引数ありのメソッドなのに無いから。
System.out.println((int)privataMethodPlusString.invoke(ReflectionManager.class.newInstance()));
// java.lang.IllegalArgumentException: object is not an instance of declaring class。StringクラスにprivateMethodは実装されてないから。
System.out.println((int)privataMethodPlusString.invoke(value, num));
java.lang.IllegalArgumentException: argument type mismatch。引数の順番が違う。実装はprivateMethod(String, int)だから。
System.out.println((int)privataMethodPlusString.invoke(ReflectionManager.class.newInstance(), num, value));

引数ありのパターンでClassの配列やObjectの配列の使い方がわかったと思います。
これらの配列の引数と実装の引数の型と順番が合わないとエラーになるんですね。

次、private staticパターンですが、非staticと一緒です。
staticなのにインスタンス必要なの?と思うかもしれませんが、同じように使ってください。

いろいろ書きましたが、ここで苦しめられたエラーケース行きます。
privateメソッドの第一引数に配列があるパターン。これが厄介でした。

// ◆private static 引数あり(第一引数にStringの配列)
Method method = ReflectionManager.class.getDeclaredMethod("privateMethodError", new Class[]{String[].class});
method.setAccessible(true);
String[] value = {"", ""};
Object[] objects = new Object[]{value};
// 実行可能
System.out.println((int)method.invoke(ReflectionManager.class.newInstance(), objects));
// java.lang.IllegalArgumentException: wrong number of arguments
System.out.println((int)method.invoke(ReflectionManager.class.newInstance(), value));

今まで実行可能だった、invokeの第二引数以降に変数直接設定パターンでエラーになりました。
しかも、「エラーの内容は引数の数が違う」という。。。
これ、最初の書き方しか知らない人だと絶対わからないですよね。
そもそも、正しいはずなんです。はず。
正しいからこそ配列以外の引数ではちゃんと動いていたんです。
invokeでデバックしたら、第二引数のObjectの配列の0番目はStringの配列でしたが、1番目に空が入ってました。勝手に入れんな。
エラーになるなら可変長引数じゃなくてObjectの配列にすればいいのに…

長くなってしまいましたが、privateを呼び出すリフレクションの書き方でした。
個人的にメインはエラーケースだったんですけど、一応正しい書き方も残しておきます。

可変長の引数のトラップに長時間ハマった自分に、さようなら。