Javaのリフレクションの例のチュートリアル
Javaのリフレクションは、アプリケーションの実行時の振る舞いを検査および変更する機能を提供します。Javaのリフレクションは、コアJavaの高度なトピックの1つです。Javaのリフレクションを使用すると、コンパイル時にアクセスできないクラス、インターフェース、列挙型を実行時に検査し、その構造、メソッド、フィールド情報を取得することができます。リフレクションを使用してオブジェクトをインスタンス化し、メソッドを呼び出し、フィールドの値を変更することもできます。
Javaリフレクション
-
- Javaのリフレクション
-
- クラスのためのJavaのリフレクション
クラスオブジェクトを取得する
スーパークラスを取得する
公開されているメンバークラスを取得する
宣言されたクラスを取得する
宣言クラスを取得する
パッケージ名を取得する
クラスの修飾子を取得する
タイプパラメータを取得する
実装されたインターフェースを取得する
すべての公開されているメソッドを取得する
すべての公開されているコンストラクタを取得する
すべての公開されているフィールドを取得する
すべての注釈を取得する
フィールドのためのJavaのリフレクション
公開されているフィールドを取得する
フィールドの宣言クラス
フィールドの型を取得する
公開されているフィールドの値を取得/設定する
プライベートフィールドの値を取得/設定する
メソッドのためのJavaのリフレクション
公開されているメソッドを取得する
公開されているメソッドを実行する
プライベートメソッドを実行する
コンストラクタのためのJavaのリフレクション
公開されているコンストラクタを取得する
コンストラクタを使用してオブジェクトをインスタンス化する
注釈のためのJavaのリフレクション
- Javaにおける反映
Javaのリフレクションは非常に強力な概念であり、通常のプログラミングではほとんど役に立たないが、JavaやJ2EEの多くのフレームワークの基礎となっています。Javaのリフレクションを使用するいくつかのフレームワークは、以下の通りです。
-
- JUnit – @Testアノテーションを解析してテストメソッドを取得し、それを実行するためにリフレクションを使用します。
-
- Spring – 依存性注入、詳細はSpringの依存性注入を参照してください。
-
- Tomcatウェブコンテナー – web.xmlファイルとリクエストURIを解析して、要求を正しいモジュールに転送します。
-
- Eclipse – メソッド名の自動補完機能
-
- Struts
- Hibernate
リストは無限であり、これらのすべてのフレームワークはユーザー定義のクラス、インターフェース、メソッドなどの知識とアクセスを持っていないため、すべてJavaのリフレクションを使用しています。クラスやインターフェースへのアクセスが既にある場合には、リフレクションは通常のプログラミングで使用しないべきです。なぜなら、以下の欠点があるからです。
- Poor Performance – Since java reflection resolve the types dynamically, it involves processing like scanning the classpath to find the class to load, causing slow performance.
- Security Restrictions – Reflection requires runtime permissions that might not be available for system running under security manager. This can cause you application to fail at runtime because of security manager.
- Security Issues – Using reflection we can access part of code that we are not supposed to access, for example we can access private fields of a class and change it’s value. This can be a serious security threat and cause your application to behave abnormally.
- High Maintenance – Reflection code is hard to understand and debug, also any issues with the code can’t be found at compile time because the classes might not be available, making it less flexible and hard to maintain.
- クラスのためのJavaリフレクション
Javaでは、すべてのオブジェクトはプリミティブ型または参照型のいずれかです。すべてのクラス、enum、配列は参照型であり、java.lang.Objectから継承しています。プリミティブ型には、boolean、byte、short、int、long、char、float、およびdoubleがあります。java.lang.Classは、リフレクション操作のエントリーポイントです。すべてのオブジェクトの種類ごとに、JVMはjava.lang.Classの不変なインスタンスをインスタンス化し、オブジェクトのランタイムプロパティを調査したり、新しいオブジェクトを作成したり、メソッドを呼び出したり、オブジェクトのフィールドを取得/設定するメソッドを提供します。このセクションでは、Classの重要なメソッドについて説明します。便宜のため、継承階層を持ついくつかのクラスとインターフェースを作成します。
package com.scdev.reflection;
public interface BaseInterface {
public int interfaceInt=0;
void method1();
int method2(String str);
}
package com.scdev.reflection;
public class BaseClass {
public int baseInt;
private static void method3(){
System.out.println("Method3");
}
public int method4(){
System.out.println("Method4");
return 0;
}
public static int method5(){
System.out.println("Method5");
return 0;
}
void method6(){
System.out.println("Method6");
}
// inner public class
public class BaseClassInnerClass{}
//member public enum
public enum BaseClassMemberEnum{}
}
package com.scdev.reflection;
@Deprecated
public class ConcreteClass extends BaseClass implements BaseInterface {
public int publicInt;
private String privateString="private string";
protected boolean protectedBoolean;
Object defaultObject;
public ConcreteClass(int i){
this.publicInt=i;
}
@Override
public void method1() {
System.out.println("Method1 impl.");
}
@Override
public int method2(String str) {
System.out.println("Method2 impl.");
return 0;
}
@Override
public int method4(){
System.out.println("Method4 overriden.");
return 0;
}
public int method5(int i){
System.out.println("Method4 overriden.");
return 0;
}
// inner classes
public class ConcreteClassPublicClass{}
private class ConcreteClassPrivateClass{}
protected class ConcreteClassProtectedClass{}
class ConcreteClassDefaultClass{}
//member enum
enum ConcreteClassDefaultEnum{}
public enum ConcreteClassPublicEnum{}
//member interface
public interface ConcreteClassPublicInterface{}
}
クラスの重要な反射メソッドをいくつか見てみましょう。
クラスオブジェクトを取得する。
オブジェクトのクラスを取得するには3つの方法があります。1つ目は、静的変数classを使用する方法です。2つ目は、オブジェクトのgetClass()メソッドを使用する方法です。また、java.lang.Class.forName(String fullyClassifiedClassName)を使用する方法もあります。プリミティブ型や配列の場合、静的変数classを使用することもできます。ラッパークラスでは、クラスを取得するためにさらに静的変数TYPEが提供されています。
// Get Class using reflection
Class<?> concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
// below method is used most of the times in frameworks like JUnit
//Spring dependency injection, Tomcat web container
//Eclipse auto completion of method names, hibernate, Struts2 etc.
//because ConcreteClass is not available at compile time
concreteClass = Class.forName("com.scdev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.scdev.reflection.ConcreteClass
//for primitive types, wrapper classes and arrays
Class<?> booleanClass = boolean.class;
System.out.println(booleanClass.getCanonicalName()); // prints boolean
Class<?> cDouble = Double.TYPE;
System.out.println(cDouble.getCanonicalName()); // prints double
Class<?> cDoubleArray = Class.forName("[D");
System.out.println(cDoubleArray.getCanonicalName()); //prints double[]
Class<?> twoDStringArray = String[][].class;
System.out.println(twoDStringArray.getCanonicalName()); // prints java.lang.String[][]
getCanonicalName()は、基底クラスの正規な名前を返します。 java.lang.Classはジェネリクスを使用しており、Classの取得がフレームワークのベースクラスのサブクラスであることを確認するのに役立ちます。ジェネリクスとワイルドカードについては、Java Generics Tutorialを参照してください。
スーパークラスを取得する。
ClassオブジェクトのgetSuperclass()メソッドは、クラスのスーパークラスを返します。このClassオブジェクトがObjectクラス、インターフェース、プリミティブ型、またはvoidを表す場合、nullが返されます。このオブジェクトが配列クラスを表す場合、Objectクラスを表すClassオブジェクトが返されます。
Class<?> superClass = Class.forName("com.scdev.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.scdev.reflection.BaseClass"
System.out.println(Object.class.getSuperclass()); // prints "null"
System.out.println(String[][].class.getSuperclass());// prints "class java.lang.Object"
パブリックメンバークラスを取得する
getClasses()メソッドは、オブジェクトのクラス表現のクラスから、そのクラスに含まれるすべてのパブリックなクラス、インターフェース、および列挙型を表すClassオブジェクトを含む配列を返します。これには、スーパークラスから継承されたパブリッククラスおよびインターフェースのメンバー、およびクラスによって宣言されたパブリッククラスおよびインターフェースのメンバーが含まれます。このメソッドは、このClassオブジェクトがパブリックなメンバークラスやインターフェースを持っていない場合、またはこのClassオブジェクトがプリミティブ型、配列クラス、またはvoidを表している場合には、長さ0の配列を返します。
Class<?>[] classes = concreteClass.getClasses();
//[class com.scdev.reflection.ConcreteClass$ConcreteClassPublicClass,
//class com.scdev.reflection.ConcreteClass$ConcreteClassPublicEnum,
//interface com.scdev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//class com.scdev.reflection.BaseClass$BaseClassInnerClass,
//class com.scdev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));
クラスを宣言する
getDeclaredClasses()メソッドは、このClassオブジェクトが表すクラスのメンバーとして宣言されたすべてのクラスとインターフェースを反映したClassオブジェクトの配列を返します。返される配列には、継承されたクラスやインターフェースで宣言されたクラスは含まれません。
//getting all of the classes, interfaces, and enums that are explicitly declared in ConcreteClass
Class<?>[] explicitClasses = Class.forName("com.scdev.reflection.ConcreteClass").getDeclaredClasses();
//prints [class com.scdev.reflection.ConcreteClass$ConcreteClassDefaultClass,
//class com.scdev.reflection.ConcreteClass$ConcreteClassDefaultEnum,
//class com.scdev.reflection.ConcreteClass$ConcreteClassPrivateClass,
//class com.scdev.reflection.ConcreteClass$ConcreteClassProtectedClass,
//class com.scdev.reflection.ConcreteClass$ConcreteClassPublicClass,
//class com.scdev.reflection.ConcreteClass$ConcreteClassPublicEnum,
//interface com.scdev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));
クラスの宣言を取得
getDeclaringClass()メソッドは、それが宣言されたクラスを示すクラスオブジェクトを返します。
Class<?> innerClass = Class.forName("com.scdev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//prints com.scdev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());
パッケージ名を取得する
getPackage() メソッドは、このクラスのパッケージを返します。このクラスのクラスローダーがパッケージを検索するために使用されます。パッケージの名前を取得するために、Package の getName() メソッドを呼び出すことができます。
//prints "com.scdev.reflection"
System.out.println(Class.forName("com.scdev.reflection.BaseInterface").getPackage().getName());
クラス修飾子を取得する
getModifiers()メソッドは、クラス修飾子のint表現を返します。source codeで使用される文字列形式で取得するには、java.lang.reflect.Modifier.toString()メソッドを使用できます。
System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//prints "public abstract interface"
System.out.println(Modifier.toString(Class.forName("com.scdev.reflection.BaseInterface").getModifiers()));
型パラメータを取得
getTypeParameters()は、クラスに関連付けられているTypeパラメーターがある場合、TypeVariableの配列を返します。Typeパラメーターは、宣言と同じ順序で返されます。
//Get Type parameters (generics)
TypeVariable<?>[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable<?> t : typeParameters)
System.out.print(t.getName()+",");
実装されたインターフェースを取得する
getGenericInterfaces()メソッドは、ジェネリック型情報を持つクラスが実装しているインターフェースの配列を返します。また、getInterfaces()を使用して、実装されたすべてのインターフェースのクラス表現を取得することもできます。
Type[] interfaces = Class.forName("java.util.HashMap").getGenericInterfaces();
//prints "[java.util.Map<K, V>, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//prints "[interface java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));
すべての公開メソッドを取得する
getMethods()メソッドは、スーパークラスやスーパーインターフェースのパブリックメソッドを含むクラスのパブリックメソッドの配列を返します。
Method[] publicMethods = Class.forName("com.scdev.reflection.ConcreteClass").getMethods();
//prints public methods of ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));
すべての公開コンストラクターを取得する
getConstructors() メソッドは、オブジェクトのクラス参照の公開されたコンストラクタのリストを返します。
//Get All public constructors
Constructor<?>[] publicConstructors = Class.forName("com.scdev.reflection.ConcreteClass").getConstructors();
//prints public constructors of ConcreteClass
System.out.println(Arrays.toString(publicConstructors));
すべての公開フィールドを取得する
getFields()メソッドは、自身のスーパークラスとスーパーインターフェースのパブリックフィールドを含むクラスの配列を返します。
//Get All public fields
Field[] publicFields = Class.forName("com.scdev.reflection.ConcreteClass").getFields();
//prints public fields of ConcreteClass, it's superclass and super interfaces
System.out.println(Arrays.toString(publicFields));
すべての注釈を取得してください。
getAnnotations()メソッドは、要素のすべてのアノテーションを返します。クラス、フィールド、メソッドでも使用できます。ただし、リフレクションで利用可能なアノテーションは、実行時の保持方針であることに注意してください。Javaアノテーションチュートリアルを参照してください。後のセクションで詳しく調べていきます。
java.lang.annotation.Annotation[] annotations = Class.forName("com.scdev.reflection.ConcreteClass").getAnnotations();
//prints [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));
- フィールドに対するJavaリフレクション
Reflection APIは、クラスのフィールドを分析し、実行時に値を変更するためのいくつかのメソッドを提供しています。このセクションでは、メソッドに関する一般的に使用されるリフレクション機能のいくつかを見ていきます。
公開フィールドを取得する。
前のセクションでは、クラスのすべてのパブリックフィールドのリストを取得する方法について説明しました。Reflection APIは、getField()メソッドを介してクラスの特定のパブリックフィールドを取得するためのメソッドも提供しています。このメソッドは、指定されたクラス参照、スーパーインターフェース、そしてスーパークラスの順にフィールドを検索します。
Field field = Class.forName("com.scdev.reflection.ConcreteClass").getField("interfaceInt");
上記の呼び出しは、ConcreteClassによって実装されたBaseInterfaceのフィールドを返します。フィールドが見つからない場合は、NoSuchFieldExceptionがスローされます。
クラスを宣言するフィールド
fieldオブジェクトのgetDeclaringClass()を使用することで、フィールドを宣言しているクラスを取得することができます。
try {
Field field = Class.forName("com.scdev.reflection.ConcreteClass").getField("interfaceInt");
Class<?> fieldClass = field.getDeclaringClass();
System.out.println(fieldClass.getCanonicalName()); //prints com.scdev.reflection.BaseInterface
} catch (NoSuchFieldException | SecurityException e) {
e.printStackTrace();
}
フィールドのタイプを取得する
getType()メソッドは、宣言されたフィールドの型に対応するClassオブジェクトを返します。もしフィールドがプリミティブ型である場合、ラッパークラスのオブジェクトを返します。
Field field = Class.forName("com.scdev.reflection.ConcreteClass").getField("publicInt");
Class<?> fieldType = field.getType();
System.out.println(fieldType.getCanonicalName()); //prints int
パブリックフィールドの値を取得/設定する
リフレクションを使って、オブジェクトのフィールドの値を取得したり設定したりすることができます。
Field field = Class.forName("com.scdev.reflection.ConcreteClass").getField("publicInt");
ConcreteClass obj = new ConcreteClass(5);
System.out.println(field.get(obj)); //prints 5
field.setInt(obj, 10); //setting field value to 10 in object
System.out.println(field.get(obj)); //prints 10
get()メソッドはオブジェクトを返すため、フィールドがプリミティブ型であれば、それに対応するラッパークラスを返します。フィールドが静的である場合、get()メソッドにはオブジェクトをnullとして渡すことができます。フィールドにオブジェクトを設定するためのいくつかのset*()メソッドがあり、フィールドに異なる型のプリミティブ型を設定することもできます。フィールドの型を取得し、適切な関数を呼び出してフィールドの値を正しく設定することができます。フィールドがfinalである場合、set()メソッドはjava.lang.IllegalAccessExceptionをスローします。
プライベートフィールドの値を取得/設定する
クラスの外部からはプライベートなフィールドやメソッドにはアクセスできないということはわかっていますが、リフレクションを使用することで、フィールド修飾子のJavaアクセスチェックをオフにして、プライベートフィールドの値を取得または設定することができます。
Field privateField = Class.forName("com.scdev.reflection.ConcreteClass").getDeclaredField("privateString");
//turning off access check with below method call
privateField.setAccessible(true);
ConcreteClass objTest = new ConcreteClass(1);
System.out.println(privateField.get(objTest)); // prints "private string"
privateField.set(objTest, "private string updated");
System.out.println(privateField.get(objTest)); //prints "private string updated"
- メソッドのためのJavaリフレクション
リフレクションを使用することで、メソッドに関する情報を取得し、メソッドを実行することもできます。このセクションでは、メソッドを取得するための異なる方法、メソッドを実行する方法、およびプライベートメソッドにアクセスする方法について学びます。
公開されたメソッドを取得
クラスのpublicなメソッドを取得するには、getMethod()を使用することができます。メソッド名とメソッドのパラメーターの型を渡す必要があります。もしクラス内でそのメソッドが見つからない場合、反射APIはスーパークラスでそのメソッドを探します。以下の例では、HashMapのput()メソッドを反射を使って取得しています。また、例ではメソッドのパラメーターの型、メソッドの修飾子、戻り値の型を取得する方法も示しています。
Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//get method parameter types, prints "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
//get method return type, return "class java.lang.Object", class reference for void
System.out.println(method.getReturnType());
//get method modifiers
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"
公開メソッドの呼び出し
以下のコードの例では、リフレクションを使用してHashMapのputメソッドを呼び出すために、Methodオブジェクトのinvoke()メソッドを使用することができます。
Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
Map<String, String> hm = new HashMap<>();
method.invoke(hm, "key", "value");
System.out.println(hm); // prints {key=value}
もしメソッドが静的な場合、オブジェクト引数としてNULLを渡すことができます。
プライベートメソッドを呼び出す。
私たちは、getDeclaredMethod()を使用して非公開のメソッドを取得し、アクセスチェックをオフにして呼び出すことができます。以下の例は、パラメータがなく、静的なBaseClassのmethod3()を呼び出す方法を示しています。
//invoking private method
Method method = Class.forName("com.scdev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
- コンストラクタのためのJavaリフレクション
Reflector APIは、クラスのコンストラクタを分析するためのメソッドを提供し、コンストラクタを呼び出すことで新しいクラスのインスタンスを作成することができます。すでに公開されているすべてのコンストラクタを取得する方法については、すでに学んでいます。
公開されたコンストラクターを取得する
オブジェクトのクラス表現に対して、getConstructor()メソッドを使用して特定の公開されたコンストラクタを取得することができます。以下の例は、上記で定義されたConcreteClassのコンストラクタとHashMapの引数なしのコンストラクタを取得する方法を示しています。さらに、コンストラクタのパラメータータイプの配列を取得する方法も示しています。
Constructor<?> constructor = Class.forName("com.scdev.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
Constructor<?> hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
コンストラクタを使用してオブジェクトをインスタンス化する。
クラスの新しいインスタンスを作成するために、newInstance()メソッドをコンストラクタオブジェクトに使用することができます。コンパイル時にクラスの情報を持っていない場合には、リフレクションを使用してObjectに割り当て、さらにリフレクションを使用してフィールドにアクセスし、メソッドを呼び出すことができます。
Constructor<?> constructor = Class.forName("com.scdev.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
Object myObj = constructor.newInstance(10);
Method myObjMethod = myObj.getClass().getMethod("method1", null);
myObjMethod.invoke(myObj, null); //prints "Method1 impl."
Constructor<?> hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
HashMap<String,String> myMap = (HashMap<String,String>) hashMapConstructor.newInstance(null);
- 注釈のための反省
アノテーションは、Java 1.5で導入され、クラス、メソッド、フィールドのメタデータ情報を提供するために使用され、現在ではSpringやHibernateなどのフレームワークで広く使用されています。リフレクションAPIも拡張され、実行時にアノテーションを分析するためのサポートを提供しています。リフレクションAPIを使用することで、RetentionポリシーがRuntimeであるアノテーションを分析することができます。私はすでに詳細なチュートリアルをアノテーションとリフレクションAPIを使用してパースする方法について書いていますので、Javaアノテーションチュートリアルをご覧いただくことをおすすめします。これでJavaリフレクションの例のチュートリアルは以上です。チュートリアルがお気に入りになり、JavaリフレクションAPIの重要性が理解できたことを願っています。