65.リフレクションよりインターフェースを選択すべき
リフレクションはパワフルな機能だが、欠点もある
リフレクションの欠点は以下。
- コンパイル時の型チェックで受ける恩恵を失う
- リフレクティブなアクセスをするコードは洗練されておらず、冗長である
- 通常のメソッド呼び出しに比べて、リフレクションでのメソッド呼び出しは遅い
分析ツールや依存性注入フレームワーク等のリフレクションを利用した洗練されたアプリケーションはあるが、それらでもリフレクションの欠点がみえるにつれ、リフレクションから離れていってる。もし、リフレクションを使うべきか迷ったとしたら、おそらく使わないのが正しい。
リフレクションを使うケース
リフレクションはとても限られたケースで使う。
コンパイル時には得られないクラスを利用せねばならない多くのプログラムでは、コンパイル時に、そのクラスを入れる型として、適切な型、スーパークラスが存在している(Item64)。
このケースでは、リフレクティブにインスタンスを作成し、インターフェースやスーパークラス経由でそれら生成されたインスタンスにアクセスする。
例として、以下のプログラムでは、第一引数で特定される、Set<String>
のサブクラスのインスタンスを作り、他のコマンドライン引数をそのSetの中に詰め込んで標準出力する。
HashSetを第一引数にとるとランダムで表示されるが、TreeSetを第一引数にとるとアルファベット順で表示される。
package tryAny.effectiveJava;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Set;
public class Reflection {
// Reflective instantiation with interface access
public static void main(String[] args) {
// Translate the class name into a Class object
Class<? extends Set<String>> cl = null;
try {
cl = (Class<? extends Set<String>>) // Unchecked cast!
Class.forName(args[0]);
} catch (ClassNotFoundException e) {
fatalError("Class not found.");
}
// Get the constructor
Constructor<? extends Set<String>> cons = null;
try {
cons = cl.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
fatalError("No parameterless constructor");
}
// Instantiate the set
Set<String> s = null;
try {
s = cons.newInstance();
} catch (IllegalAccessException e) {
fatalError("Constructor not accessible");
} catch (InstantiationException e) {
fatalError("Class not instantiable.");
} catch (InvocationTargetException e) {
fatalError("Constructor threw " + e.getCause());
} catch (ClassCastException e) {
fatalError("Class doesn't implement Set");
}
// Exercise the set
s.addAll(Arrays.asList(args).subList(1, args.length));
System.out.println(s);
}
private static void fatalError(String msg) {
System.err.println(msg);
System.exit(1);
}
}
上記で使われるテクニックは本格的なservice provider framework(?Item1)で使われるくらい強力なものである。
だいたいこのテクニックがリフレクションで必要なすべてである。
上記には2つ欠点がある。
- 6つの実行時エラーが出うる。これらはリフレクションを使わなければコンパイル時に検出できるエラーである。
- 名称からインスタンスを生成するのに25行もかかってしまっている。通常のコンストラクタなら1行ですむのに。
上記のコードではキャスト時にwarningが出ている。
仮に第一引数に取る名称がSetの実装クラスでなくとも、Class<? extends Set<String>>
へのキャストは成功するので、この警告は正当なものである。
warningの抑制についてはItem27をみるべし。
リフレクションを使う非常にレアなケースとして、パッケージの複数のバージョンを動的に使う場合がある。
一番古いバージョンをコンパイルしておいて、動的に新しいクラスのメソッドを呼び出すなどを行う。(全然ぴんと来ない)