FF5の裏ボス、オメガは常にリフレク状態だという...すみません、話が脱線しました。今回は、Javaのリフレクションについて調査していきたいと思います。
リフレクションとは
⇧ 『改訂2版 パーフェクトJava』の書籍によると、
ということみたいです。
- クラスのロード
- クラスの型階層の列挙
- クラスや配列からオブジェクトの生清
- クラスやインターフェイスのメンバ(フィールド定義やメソッド定義など)の型や名前の取得
- フィールド値の読み書きやメソッド呼び出し
- アノテーション情報の読み出し
- プロクシクラスによる処理の差し込み
などなど、様々なことができるみたいですね。
⇧ 上記サイト様によると、リフレクションは実行速度がむちゃくちゃ遅いようです。
そんでは、リフレクションは一体どんなときに使うかというと、
⇧ 上記サイト様の説明にもあるように、単体テストにおいて真価を発揮する感じでしょうか。
リフレクションを実現するJava.lang.Class
Class (Java Platform SE 8) によると、
クラスClass<T>
- java.lang.Object
-
- java.lang.Class<T>
-
- 型パラメータ:
T
- このClass
オブジェクトでモデル化されるクラスの型。たとえば、String.class
の型はClass<String>
である。モデル化するクラスが不明な場合はClass<?>
を使用する。
- すべての実装されたインタフェース:
- Serializable, AnnotatedElement, GenericDeclaration, Type
ということみたいです。
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 プロジェクト」を選択。
「プロジェクト名(P):」を入力し、「次へ(N)>」をクリック。
「完了(F)」をクリック。
ファイルを編集。
/** * リフレクションの利用 */ 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アプリケーション」を選択。
コンソールにgetName()で取得されたクラスの名前が表示されました。
基本型のリフレクション
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)); } }
配列のリフレクション
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(); } } }
ジェネリック型のリフレクション
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()); } }
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); } }
アノテーションのリフレクション
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()); } }
リフレクションによるオブジェクトの生成
Class<T> オブジェクトから、T オブジェクトを作成することを、リフレクションによるインスタンスの生成というようですね。
リフレクションによるインスタンス(オブジェクト)の生成には、
- ClassクラスのnewInstanceメソッド
- ControllerクラスのnewInstanceメソッド
- ArrayクラスのnewInstanceメソッド
の方法があるようです。
⇧ 上記サイト様によると、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(); } } }
通常での使用とリフレクションでの使用で、メソッドの呼び出し方の比較
⇧ リフレクションでメソッドを実行する場合、第一引数にオブジェクト(今回は、StringBuilderクラスのオブジェクト)、第二引数からは、メソッドに引数がある場合、引数の数だけ指定するようです。
staticなメソッドの場合は、第一引数に null を指定すれば良いようです。
リフレクションでのメソッドの実行結果を、JUnitなどで比較すれば、privateなメソッドの単体テストも実行できそうですね。
今回はこのへんで。