リフレクションの罠

今日某MLに,次のコードで例外が投げられるというなかなか面白い問題が投稿されていたので調べてみた.

BeanMap map = Beans.createAndCopy(BeanMap.class, status).execute();

これを簡略化すると,publicなインタフェースInterfaceAを実装したデフォルトアクセスの(publicではない)実装クラスClassBがpublicなメソッドmethodCを持つ場合に,ファクトリメソッドで得たインスタンスinterfaceA(実際に返されるのはClassBのインスタンス)のmethodCをリフレクションを使って呼び出そうとすると,IllegalAccessExceptionが投げられるというものである.

Method method = interfaceA.getClass().getMethod("methodC", null);
method.invoke(interfaceA, null);

質問者は実装クラスをpublicにするか,ラッパクラスを用意しろと要求していた.しかし,「それは違うんじゃない?」と思い調べてみると,次のバグレポートが見つかった.
callinreflection gives IllegalAccessException, whereas direct call doesn't
実は正しいコードは以下の通り.つまり,interface.getClass()して返されるのはInterfaceAではなくClassBであり,これではパッケージの外から呼び出すことはできないのだ.つまり,投稿されたライブラリではなく?easar2のバグだったのだ.

Method method = InterfaceA.class.getMethod("methodC", null);
method.invoke(interfaceA, null);

さて,これから得られる教訓は以下の通り.

  1. リフレクションを使った動的なコードのバグは,コンパイラでは検出できず,実行時に例外として発覚する.
  2. コンパイラの支援が得られないので,リフレクションを使った部分のバグが発見されにくく,その部分のコードの品質が低くなりがちである.
  3. 通常はExceptionが発生したコードのバグを疑うべきなのに,質問者は別のライブラリの仕様の問題だと勘違いしてしまった.リフレクションを使った動的コードを多用するようなプログラマは優秀だとか,同じOSSでもよく知られている方が品質が高いというようないう勘違いが生じやすいのかもしれない.