【Effective Java】項目53:リフレクションよりインタフェースを選ぶ

リフレクション機構

リフレクション機構を使うと、ロードされたクラスの情報に、プログラムからアクセスすることができます。

具体的には Class インスタンスに対して、それが表すクラスのコンストラクタ・メソッド・フィールドを取得することができます。 それぞれ Constructor クラス、Method クラス、Field クラスが対応しています。 Constructor インスタンスや Method インスタンスに対して、実行処理を行うこともできます。

リフレクション機能はコンパイルされた時点で存在しないクラスを利用することを可能にします。 一方、次のようなデメリットも持っています。

  • コンパイルの型検査による恩恵を失う
  • リフレクションの実行コードは冗長
  • 実行パフォーマンスが劣化する

通常のアプリケーションでは、リフレクションを使うことはまれです。

リフレクションはクラスブラウザやコード解析ツールなどで利用されます。 また、フレームワークや高度なシステムプログラミングなどの一部の分野でみ利用されます。

インタフェースによるアクセス

実行時、リフレクションを使ってオブジェクトにアクセスするべきではありません。 たとえ、オブジェクトの生成をリフレクションでおこなうとしても、そのオブジェクトへのアクセスはインタフェースを使って行うべきです。

次のコードは実行時に引数を元にして任意の Set 実装インスタンスを生成し、引数リストをセットするプログラムです。

public static void main(String[] args) {
    Class<?> cl = null;
    try {
        cl = Class.forName(args[0]);
    } catch (ClassNotFoundException e) {
        System.err.println("Class not found: " + args[0]);
        // 通常のアプリケーションでは exit による終了よくないので注意
        System.exit(1);
    }

    // インスタンスの生成はリフレクションで行う
    Set<String> set = null;
    try {
        set = (Set<String>) cl.newInstance();
    } catch (IllegalAccessException e) {
        System.err.println("Class not accessible");
        System.exit(1);
    } catch (InstantiationException e) {
        System.err.println("Class not instantiable");
        System.exit(1);
    }

    // インスタンスへのアクセスはインタフェース経由で行う
    set.addAll(Arrays.asList(args).subList(1, args.length));
    System.out.println(set);
}

インスタンス生成はリフレクションで行って、その後のアクセスは通常の呼び出しを行っています。 こうするとコンパイラによる型検査の恩恵を受けることができます。

もし、コンストラクタにパラメータがないものを生成するのであれば、java.lang.reflect は必要ありません。 Class.newInstance メソッドのみで利用可能です。

このコードを見ると、リフレクションの短所が分かります。

まず、実行時エラーを発生させる可能性があることです。 実行時エラーをケアするために長い try-catch が必要になります。

もう一つはコードが冗長になります。 インスタンス生成が1行で収まる場面で、20行ほど必要です。 ただし、一旦インスタンス化されてしまえば、通常の Set と同様に使うことができます。