18 Nov 2014, 13:45

Java のリフレクションでインスタンスやメソッドを動的生成する

リフレクションとは

リフレクションとは, プログラム実行中に, クラス名やメソッド名を動的に指定することができる技術.

以下, Wikipedia のソースをそのまま引用します.

// リフレクションなし
Foo foo = new Foo ();
foo.hello ();

// リフレクション
Class cl = Class.forName ("Foo");
Method method = cl.getMethod ("hello");
method.invoke (cl.newInstance ());

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

リフレクションはカプセル化を壊す?

リフレクションを利用すると, クラス内部のメソッドやフィールドをみたり, フィールドを書き換えたりできるという, ハッカー的な機能.

こういうオブジェクト指向に反する機能は, ユーティリティクラスのメソッド呼び出しに効果的である.

リフレクションは遅い??

リフレクションは遅いということがいわれている.以下のサイトが参考になる.

  • リフレクションでのオブジェクトの作成はオーバーヘッドがほとんどない
  • メンバーフィールドにアクセスするのは遅すぎるのでやらないほうがいい.
  • メソッド呼び出しは 5 から 20 倍遅いので, 使う場合は注意する

コンパイル時の型チェックができない

リフレクションはプログラム実行時にメソッド呼び出し方法が決まるので, コンパイル時の事前型チェックができない.

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

Effective Java p233 にのっている助言.

Java でのリフレクション

java.lang.reflect というパッケージがある.

リフレクションでインスタンス生成

  • Class.forName (“クラス名”) で クラス生成
  • newInstance () で インスタンス生成
Class clazz = Class.forName ("Foo");
Foo foo = (Foo) clazz.newInstance ();

リフレクションでメソッド呼び出し

  • getMethod (“メソッド名”) でメソッド定義
  • invoke (object) でメソッド呼び出し
Foo foo2 = new Foo ();
Method method = foo2.getClass ().getMethod ("bar");
method.invoke (foo2);

Code

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class ReflectionSample {  
    public static void main (String []args) {  
        try {  
            Class clazz = Class.forName ("Foo");
            Foo foo = (Foo) clazz.newInstance ();
            foo.bar ();
        } catch (ClassNotFoundException e) {
            // クラスが存在しない
            e.printStackTrace ();  
        } catch (InstantiationException e) {
            // インスタンス作成不可
            e.printStackTrace ();  
        } catch (IllegalAccessException e) {
            // 呼び出し:アクセス違反, 保護されている         
            e.printStackTrace ();  
        }

        Foo foo2 = new Foo ();
        try {
            // 引数なし
            Method method = foo2.getClass ().getMethod ("bar");
            method.invoke (foo2);

            // 引数あり
            Method method2 = foo2.getClass ().getMethod ("pee", int.class);
            method2.invoke (foo2, 1);
        } catch (NoSuchMethodException e) {
            // メソッドが存在しない
            e.printStackTrace ();
        } catch (IllegalArgumentException e) {
            // 呼び出し:引数が異なる
            e.printStackTrace ();
        } catch (IllegalAccessException e) {
            // 呼び出し:アクセス違反, 保護されている
            e.printStackTrace ();
        } catch (InvocationTargetException e) {
            // ターゲットとなるメソッド自身の例外処理
            e.printStackTrace ();
        }
    }
}

class Foo {
    public Foo () {
        System.out.println ("Constructor is called");
    }

    public void bar () {
        System.out.println ("method is called");
    }

    public void pee (int i) {
        System.out.println (i);
    }
}

Special Thanks