リフレクションよりインタフェースを選ぶ

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)

Effective Java 項目53 リフレクションよりインタフェースを選ぶ  のまとめ。

ソースコード

effectivejava/src/effectivejava53 at master · snoopopo/effectivejava · GitHub

リフレクションのデメリット

例外の検査も含めて、コンパイル時の型検査の恩恵をすべて失います。 (page.223)

コンパイル時の型検査の恩恵」といっているのは、 例えば、以下のようにインスタンス生成しているけどHogeクラスがHogeIFの実装クラスじゃなければ、実行時に例外が発生する。

Class cl = Class.forName("study.Hoge");
HogeIf hoge = (HogeIf)cl.newInstance(); //ここでClassCastExceptionが発生する!

リフレクションを使わず、普通にインスタンス生成したら、コンパイル時にエラーが起こる。*1

 HogeIf hoge2 = new Hoge();

コンパイル時にエラーに気付くことが出来ないことがデメリットということを言っているようだ。

リフレクションによるアクセスを行うコードはぎこちなく、冗長です。 (page.223)

リフレクションを使用すると、普段は一行で済むような処理でも複数行に書かないといけなくなる。ひとつのメソッドを呼ぶだけでもこれだけの行が必要になる。

Class cl = Class.forName("study.effectivejava53.Hoge");
HogeIf hoge = (HogeIf)cl.newInstance();

Method method = hoge.getClass().getMethod("test", null);
method.invoke(hoge, null);

パフォーマンスが悪くなります。 (page.223)

これは一番よく聞くけど実際に試したことがなかったから、ちょっと試してみた。

リフレクションからメソッドを呼んだ場合と、ふつうにインスタンス生成をしてメソッドを読んだ場合を比較する。メソッドの中身はSystem.out.printlnで文字列"hogehoge"を出力するだけ。

10000回メソッドを呼ぶ、というのを3回やった結果だ。*2

---リフレクションで呼ぶ 1回目:1239---ふつうにメソッド呼ぶ 1回目:125
---リフレクションで呼ぶ 2回目:1457---ふつうにメソッド呼ぶ 2回目:445
---リフレクションで呼ぶ 3回目:2011---ふつうにメソッド呼ぶ 3回目:78

状況によって差にブレはもちろんあるけど、ふつうにメソッドを呼ぶよりリフレクションが遅いのは変わらなかった。

リフレクションはどういうときに使う?

基本的には、使用するのが適切なケースは少ない。

リフレクションを使用するのが適切なケースとして、以下をあげている。

たとえば、クラスブラウザー、オブジェクトインスペクター、コード解析ツール、インタプリタ的組み込みシステムなどです。スタブコンパイラの必要性をなくすためにリモート・プロシージャ・コール(RPC)システムでリフレクションが使用されるのも適切です。 (page.223)

ん?ちょっとすぐにわからなそうだ。→TODO

ただ、僕がこれらがちんぷんかんぷんなのは今まで携わってきたプログラムはこれにあてはまらないからなんだろう。よって使うべきところではなさそうだ。

リフレクションよりインターフェースを選ぶ

使うなら、どういう風に使う?かというと 

できる限り、オブジェクトのインスタンス化のためだけにリフレクションを使用し、 コンパイル時にわかっているインタフェースやスーパークラスを使用してオブジェクトへアクセスすべきです。 (page.225)

例(インスタンス生成時のみリフレクションを使用)

private static void example(String className)
throws ClassNotFoundException, InstantiationException, IllegalAccessException{

Class cl = Class.forName(className);
HogeIf hoge = (HogeIf)cl.newInstance(); //インスタンス生成のときのみリフレクションを使う
hoge.test(); //Ifで実装へアクセス
}

悪い例(メソッド実行でもリフレクションを使用してしまっている)

private static void badExample(String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException{

Class cl = Class.forName(className);
Object hoge = cl.newInstance();

Method method = hoge.getClass().getMethod("test", null);
method.invoke(hoge, null);
} 

リフレクションを使う部分を少なくすることで実行時に起こりうる例外も少なくなる!

まとめ

・リフレクションは適用すべきところ以外で使用するにはデメリットが大きすぎる。

・使用する場合は、インスタンス生成までにとどめ、IFやスーパークラスを使用してメソッド、フィールドへのアクセスを行うこと。

2014/11/10 add. > ソースコードのリンク追加.

*1:eclipseだと「型の不一致: Hoge から HogeIf には変換できません」と出た。

*2:System.currentTimeMillis()を処理の直前直後に呼んで差分をとった。