Java リフレクションとは? FF(ファイナルファンタジー)のリフレクとは関係ない

FF5の裏ボス、オメガは常にリフレク状態だという...すみません、話が脱線しました。今回は、Javaのリフレクションについて調査していきたいと思います。

リフレクションとは

gihyo.jp

⇧  『改訂2版 パーフェクトJava』の書籍によると、 

リフレクションは実行中に型情報を取得し、型そのものを操作対象にできる仕組みです。型とは、クラス(Enum型含む)、インターフェイス、配列型、基本型を指します。

ということみたいです。

  • クラスのロード
  • クラスの型階層の列挙
  • クラスや配列からオブジェクトの生清
  • クラスやインターフェイスのメンバ(フィールド定義やメソッド定義など)の型や名前の取得
  • フィールド値の読み書きやメソッド呼び出し
  • アノテーション情報の読み出し
  • プロクシクラスによる処理の差し込み

などなど、様々なことができるみたいですね。

 

blog.y-yuki.net

⇧  上記サイト様によると、リフレクションは実行速度がむちゃくちゃ遅いようです。

そんでは、リフレクションは一体どんなときに使うかというと、

 

fantastic-works.com

⇧  上記サイト様の説明にもあるように、単体テストにおいて真価を発揮する感じでしょうか。 

 

リフレクションを実現するJava.lang.Class

 Class (Java Platform SE 8) によると、

java.lang

クラスClass<T>

  • 型パラメータ:
    T - このClassオブジェクトでモデル化されるクラスの型。たとえば、String.classの型はClass<String>である。モデル化するクラスが不明な場合はClass<?>を使用する。
    すべての実装されたインタフェース:
    SerializableAnnotatedElementGenericDeclarationType

ということみたいです。

 

Classオブジェクトの生成 

Classオブジェクトを生成するには、決まり事があり、

  • new式は使えない。
  • 何らかのクラスをロードすることで自動的に生成。

ということみたいです。 

 

◇ Stringクラスを利用したいなら、

    Class<String>

◇ StringBuilderクラスを利用したいなら、

    Class<StringBuilder>

といったような記述をすることで、Classオブジェクトが生成されることになるようです。

 

Classオブジェクトの参照

Classオブジェクトの参照の取得は、クラスローダーが保持するClassオブジェクトの中から検索されるようです。

Classオブジェクトの参照の取得方法には4つほど方法があるようで、

  • クラスリテラル
  • ObjectクラスのgetClassメソッド
  • ClassクラスのforNameメソッド
  • その他(ClassクラスやClassLoaderクラスのメソッドを経由)

実際に、StringBuilderクラスをリフレクションしてみます。

EclipseパースペクティブJavaになってるのを確認してから、「ファイル(F)」>「新規(N)」>「Java プロジェクト」を選択。

f:id:ts0818:20171229213045j:plain

「プロジェクト名(P):」を入力し、「次へ(N)>」をクリック。

f:id:ts0818:20171229220703j:plain

「完了(F)」をクリック。

f:id:ts0818:20171229220406j:plain

 ファイルを編集。

/**
 * リフレクションの利用
 */
public class ReflectSample {

    public static void main(String[] args) {

        try {

            // クラスリテラルの場合
            Class<StringBuilder> clazz01 = StringBuilder.class;

            // ObjectクラスのgetClassメソッドの場合
            StringBuilder sb = new StringBuilder();
            Class<? extends StringBuilder> clazz02 = sb.getClass();

            // ClassクラスのforNameメソッドの場合
            Class<?> clazz03 = Class.forName("java.lang.StringBuilder");

            System.out.println(clazz01.getName());
            System.out.println(clazz02.getName());
            System.out.println(clazz03.getName());

        } catch (ReflectiveOperationException e) {

            e.printStackTrace();
        }
    }

}

「実行(R)」>「2 Javaアプリケーション」を選択。

f:id:ts0818:20171229221111j:plain

コンソールにgetName()で取得されたクラスの名前が表示されました。

f:id:ts0818:20171229212746j:plain

 

基本型のリフレクション

int型のClassオブジェクトを例にとると、ジェネリック型の制約により、Class<int>という記述はできないので、Class<Integer>という形になります。

Integer型のClassオブジェクトもClass<Integer>という記述になるので注意が必要そうです。

public class ReflectSample01 {

    public static void main(String[] args) {

        // int型の場合
        Class<Integer> clazz01 = int.class;
        Class<Integer> clazz02 = Integer.TYPE;

        // Integer型の場合
        Class<Integer> clazz03 = Integer.class;

        //
        System.out.println("型: " + clazz01.getName() + "\n" + "ハッシュコード: " + System.identityHashCode(clazz01));
        System.out.println("型: " + clazz02.getName() + "\n" + "ハッシュコード: " + System.identityHashCode(clazz02));
        System.out.println("型: " + clazz03.getName() + "\n" + "ハッシュコード: " + System.identityHashCode(clazz03));

    }

}

f:id:ts0818:20180101084205j:plain

 

配列のリフレクション

 

public class ReflectSample02 {

    public static void main(String[] args) {

        try {
            // String型の配列
            String[] stringArray = {};
            Class<String[]> clazz01 = String[].class;
            Class<? extends String[]> clazz02 = stringArray.getClass();
            Class<?> clazz03 = Class.forName("[Ljava.lang.String;");

            // Integer型の配列
            Integer[] integerArray = {};
            Class<Integer[]> clazz04 = Integer[].class;
            Class<? extends Integer[]> clazz05 = integerArray.getClass();
            Class<?> clazz06 = Class.forName("[Ljava.lang.Integer;");

            // int型の配列
            int[] intArray = {};
            Class<int[]> clazz07 = int[].class;
            Class<? extends int[]> clazz08 = intArray.getClass();
            Class<?> clazz09 = Class.forName("[I");

            // String型の配列
            System.out.println(clazz01.getName());
            System.out.println(clazz02.getName());
            System.out.println(clazz03.getName() + "\n");

            // Integer型の配列
            System.out.println(clazz04.getName());
            System.out.println(clazz05.getName());
            System.out.println(clazz06.getName() + "\n");

            // int型の配列
            System.out.println(clazz07.getName());
            System.out.println(clazz08.getName());
            System.out.println(clazz09.getName());


        } catch (ClassNotFoundException e) {

            e.printStackTrace();
        }

    }

}

f:id:ts0818:20180101091653j:plain

 

ジェネリック型のリフレクション

 

import java.util.ArrayList;
import java.util.List;

public class ReflectSample03 {

    public static void main(String[] args) {
            
            List arrayList = new ArrayList<>();
            List<String> stringList = new ArrayList<>();
            List<Integer> integerList = new ArrayList<>();

            Class<ArrayList> clazz01 = ArrayList.class;
            Class<?> clazz02 = arrayList.getClass();
            Class<? extends List> clazz03 = stringList.getClass();
            Class<? extends List> clazz04 = integerList.getClass();
            Class<?> clazz05 = null;

        try {

            clazz05 = Class.forName("java.util.ArrayList");
            
        } catch (ClassNotFoundException e) {

            e.printStackTrace();
        }

        System.out.println(clazz01.getName());
        System.out.println(clazz02.getName());
        System.out.println(clazz03.getName());
        System.out.println(clazz04.getName());
        System.out.println(clazz05.getName());

    }

}
    

f:id:ts0818:20180101094141j:plain

 

Class<List>とClass<ArrayList>

import java.util.ArrayList;
import java.util.List;

public class ReflectSample04 {

    public static void main(String[] args) {
        
        Class<List> clazz01 = List.class;
        Class<?> clazz02 = null;

        Class<ArrayList> clazz03 = ArrayList.class;
        Class<?> clazz04 = null;

        try {
            clazz02 = Class.forName("java.util.List");
            clazz04 = Class.forName("java.util.ArrayList");

        } catch (ClassNotFoundException e) {

            e.printStackTrace();
        }

        System.out.println(clazz01);
        System.out.println(clazz02);
        System.out.println(clazz03);
        System.out.println(clazz04);

    }

}

f:id:ts0818:20180101095845j:plain

 

アノテーションのリフレクション

public class ReflectSample05 {

	public static void main(String[] args) {

		Class<Override> clazz01 = Override.class;
		Class<?> clazz02 = null;

		try {
			clazz02 = Class.forName("java.lang.Override");

		} catch (ClassNotFoundException e) {

			e.printStackTrace();
		}
		System.out.println(clazz01.getName());
		System.out.println(clazz02.getName());


	}

}
    

f:id:ts0818:20180101100612j:plain

 

リフレクションによるオブジェクトの生成

Class<T> オブジェクトから、T オブジェクトを作成することを、リフレクションによるインスタンスの生成というようですね。

リフレクションによるインスタンス(オブジェクト)の生成には、

  • ClassクラスのnewInstanceメソッド
  • ControllerクラスのnewInstanceメソッド
  • ArrayクラスのnewInstanceメソッド

の方法があるようです。

blog.y-yuki.net

⇧  上記サイト様によると、ClassクラスのnewInstanceメソッドはJava 9から非推奨になるみたいですね。 

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ReflectSample06 {

    public static void main(String[] args) {
        
        Class<?> clazz = StringBuilder.class;

        try {
            Constructor<?> constructor = clazz.getConstructor(String.class);
            StringBuilder sb = (StringBuilder)constructor.newInstance("2018年");
            System.out.println(sb);

        } catch (NoSuchMethodException e) {
            
            e.printStackTrace();
            
        } catch (SecurityException e) {
            
            e.printStackTrace();
            
        } catch (InstantiationException e) {
            
            e.printStackTrace();
            
        } catch (IllegalAccessException e) {
            
            e.printStackTrace();
            
        } catch (IllegalArgumentException e) {
            
            e.printStackTrace();
            
        } catch (InvocationTargetException e) {
            
            e.printStackTrace();
        }
    }
}

 

リフレクションによるメソッド呼び出し

 

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

public class reflectSample07 {


    public static void main(String[] args) {
        try {

            // Classオブジェクト
            Class<?> clazz = StringBuilder.class;
            // リフレクションでコンストラクタを取得
            // public java.lang.StringBuilder(java.lang.String)
            Constructor<?> constructor = clazz.getConstructor(String.class);
            // コンストラクタでインスタンス生成し、StringBuilder型にキャスト
            StringBuilder sb = (StringBuilder) constructor.newInstance("2018年");

            // StringBuilderのappendメソッド取得
            Method append = clazz.getMethod("append", String.class);
            // リフレクションでappendメソッドを実行
            Object result = append.invoke(sb, "1月1日");

            System.out.println(result);

        } catch (NoSuchMethodException e) {

            e.printStackTrace();

        } catch (SecurityException e) {

            e.printStackTrace();

        } catch (InstantiationException e) {

            e.printStackTrace();

        } catch (IllegalAccessException e) {

            e.printStackTrace();

        } catch (IllegalArgumentException e) {

            e.printStackTrace();

        } catch (InvocationTargetException e) {

            e.printStackTrace();
        }


    }

}

通常での使用とリフレクションでの使用で、メソッドの呼び出し方の比較

f:id:ts0818:20180113144742p:plain

⇧  リフレクションでメソッドを実行する場合、第一引数にオブジェクト(今回は、StringBuilderクラスのオブジェクト)、第二引数からは、メソッドに引数がある場合、引数の数だけ指定するようです。 

staticなメソッドの場合は、第一引数に null を指定すれば良いようです。

 

リフレクションでのメソッドの実行結果を、JUnitなどで比較すれば、privateなメソッドの単体テストも実行できそうですね。

今回はこのへんで。