Javaのリフレクションは、アプリケーションの実行時の振る舞いを検査および変更する機能を提供します。Javaのリフレクションは、コアJavaの高度なトピックの1つです。Javaのリフレクションを使用すると、クラス、インターフェース、列挙型の構造、メソッド、フィールドの情報をコンパイル時にアクセスできない場合でも、実行時に検査することができます。また、リフレクションを使用してオブジェクトをインスタンス化し、そのメソッドを呼び出し、フィールドの値を変更することもできます。
Javaのリフレクション
- Javaのリフレクション
- クラスのJavaリフレクション
- フィールドのためのJavaリフレクション
- メソッドのためのJavaリフレクション
- コンストラクタのためのJavaリフレクション
- アノテーション用のJavaリフレクション
Javaにおけるリフレクションは非常に強力な概念であり、通常のプログラミングではほとんど使用されませんが、ほとんどのJava、J2EEフレームワークの基盤となっています。Javaリフレクションを使用するいくつかのフレームワークは次のとおりです:
- JUnit – テストメソッドを取得し、それを呼び出すために@Testアノテーションを解析するためにリフレクションを使用します。
- Spring – 依存性の注入、詳細についてはSpring Dependency Injectionを参照してください。
- Tomcat – web.xmlファイルとリクエストURIを解析して、リクエストを正しいモジュールに転送するためにリフレクションを使用します。
- Eclipse – メソッド名の自動補完
- Struts
- Hibernate
リストは無限で、これらすべてのフレームワークはユーザー定義のクラス、インタフェース、メソッドなどに関する知識とアクセスがないため、すべてJavaのリフレクションを使用します。すでにクラスとインタフェースにアクセスできる場合、通常のプログラミングではリフレクションを使用しないべきです。なぜなら、以下の欠点があるからです。
- パフォーマンスの低下 – Javaのリフレクションは型を動的に解決するため、クラスパスをスキャンしてクラスを見つけてロードするなどの処理が必要で、パフォーマンスが低下します。
- セキュリティ制限 – リフレクションには実行時の許可が必要であり、セキュリティマネージャの下で実行されているシステムでは利用できない場合があります。これにより、セキュリティマネージャのためにアプリケーションが実行時に失敗する可能性があります。
- セキュリティの問題 – リフレクションを使用すると、アクセスすべきではないコードの一部にアクセスできます。たとえば、クラスのプライベートフィールドにアクセスして値を変更することができます。これは重大なセキュリティの脅威となり、アプリケーションの異常動作を引き起こす可能性があります。
- メンテナンスの難しさ – リフレクションコードは理解とデバッグが困難であり、また、コードの問題をコンパイル時に見つけることはできません。なぜなら、クラスが利用できない可能性があるためです。これにより、柔軟性が低く、メンテナンスが困難になります。
Javaでは、すべてのオブジェクトはプリミティブ型または参照型です。すべてのクラス、列挙型、配列は参照型であり、java.lang.Object
を継承しています。プリミティブ型には、boolean、byte、short、int、long、char、float、およびdoubleがあります。java.lang.Classは、リフレクション操作のエントリーポイントです。すべてのオブジェクトのタイプに対して、JVMはjava.lang.Class
の不変のインスタンスをインスタンス化し、オブジェクトのランタイムプロパティを調べたり、新しいオブジェクトを作成したり、メソッドを呼び出したり、オブジェクトフィールドを取得/設定したりするためのメソッドを提供します。このセクションでは、Classの重要なメソッドを見ていきます。便宜上、いくつかのクラスとインターフェースを継承の階層構造で作成しています。
package com.journaldev.reflection;
public interface BaseInterface {
public int interfaceInt=0;
void method1();
int method2(String str);
}
package com.journaldev.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");
}
// 内部公共类
public class BaseClassInnerClass{}
//成员公共枚举
public enum BaseClassMemberEnum{}
}
package com.journaldev.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;
}
// 内部类
public class ConcreteClassPublicClass{}
private class ConcreteClassPrivateClass{}
protected class ConcreteClassProtectedClass{}
class ConcreteClassDefaultClass{}
//成员枚举
enum ConcreteClassDefaultEnum{}
public enum ConcreteClassPublicEnum{}
//成员接口
public interface ConcreteClassPublicInterface{}
}
让我们来看一些重要的反射类方法。
获取类对象
我们可以通过三种方法来获取对象的类 – 通过静态变量class
,使用对象的getClass()
方法,以及java.lang.Class.forName(String fullyClassifiedClassName)
。对于原始类型和数组,我们可以使用静态变量class
。包装类提供另一个静态变量TYPE
来获取类。
//使用反射获取类
Class > concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
//下面的方法在像JUnit这样的框架中经常使用
//Spring依赖注入,Tomcat Web容器
//Eclipse自动完成方法名称,Hibernate,Struts2等
//因为ConcreteClass在编译时不可用
concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass
//对于原始类型,包装类和数组
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 はジェネリクスを使用しており、フレームワークが取得するクラスがフレームワークのベースクラスのサブクラスであることを確認するのに役立ちます。Java ジェネリクスチュートリアルを参照して、ジェネリクスとワイルドカードについて学んでください。
スーパークラスを取得
getSuperclass() メソッドは、クラスオブジェクトのスーパークラスを返します。このクラスが Object クラス、インターフェース、プリミティブ型、または void を表す場合は、null が返されます。このオブジェクトが配列クラスを表す場合は、Object クラスを表す Class オブジェクトが返されます。
Class<?> superClass = Class.forName("com.journaldev.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.journaldev.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オブジェクトがパブリックなメンバークラスまたはインタフェースを持たない場合、またはこのClassオブジェクトがプリミティブ型、配列クラス、またはvoidを表す場合、長さ0の配列を返します。
Class >[] classes = concreteClass.getClasses();
//[class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum,
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//class com.journaldev.reflection.BaseClass$BaseClassInnerClass,
//class com.journaldev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));
宣言されたクラスを取得する
getDeclaredClasses()
メソッドは、このClassオブジェクトが表すクラスのメンバーとして宣言されたすべてのクラスおよびインタフェースを反映するClassオブジェクトの配列を返します。返される配列には、継承されたクラスやインタフェースで宣言されたクラスは含まれません。
//ConcreteClass
Class >[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
で明示的に宣言されているすべてのクラス、インターフェース、および列挙型を取得する
//出力は[class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum,
System.out.println(Arrays.toString(explicitClasses));
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
getDeclaringClass()
メソッドは、それが宣言されたクラスを表すClassオブジェクトを返します。
Class > innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//出力はcom.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());
パッケージ名の取得
getPackage()
メソッドは、このクラスのパッケージを返します。このクラスのクラスローダーはパッケージを見つけるために使用されます。PackageのgetName()
メソッドを呼び出すことで、パッケージの名前を取得できます。
//「com.journaldev.reflection」を出力します
System.out.println(Class.forName("com.journaldev.reflection.BaseInterface").getPackage().getName());
クラスの修飾子を取得する
getModifiers()
メソッドは、クラスの修飾子のint表現を返します。ソースコードで使用される文字列形式で取得するためにjava.lang.reflect.Modifier.toString()
メソッドを使用できます。
System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//「public abstract interface」を出力します
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers()));
型パラメータを取得する
getTypeParameters()
メソッドは、クラスに関連付けられている型パラメータがある場合、TypeVariableの配列を返します。型パラメータは宣言された順序と同じ順序で返されます。
//型パラメータ(ジェネリック)を取得する
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();
//印刷「[java.util.Map、interface java.lang.Cloneable、interface java.io.Serializable]」
System.out.println(Arrays.toString(interfaces));
//印刷「[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.journaldev.reflection.ConcreteClass").getMethods();
//ConcreteClass、BaseClass、Objectの公開メソッドを印刷する
System.out.println(Arrays.toString(publicMethods));
すべての公開コンストラクタを取得する
getConstructors()
メソッドは、オブジェクトのクラス参照の公開コンストラクタのリストを返します。
//すべての公開コンストラクタを取得する
Constructor >[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//ConcreteClassの公開コンストラクタを印刷する
System.out.println(Arrays.toString(publicConstructors));
すべての公開フィールドを取得する
getFields()
メソッドは、そのクラスの公開フィールド(スーパークラスとスーパーインターフェースの公開フィールドも含む)の配列を返します。
//すべてのパブリックフィールドを取得します
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//ConcreteClass、そのスーパークラス、およびスーパーインターフェースのパブリックフィールドを表示します
System.out.println(Arrays.toString(publicFields));
すべての注釈を取得します
getAnnotations()
メソッドは、要素のすべての注釈を返します。これはクラス、フィールド、およびメソッドに使用することができます。ただし、反映で利用可能な注釈はRUNTIMEの保持ポリシーを持つものだけです。詳細については、Javaアノテーションチュートリアルを参照してください。後のセクションでこれについて詳しく見ていきます。
java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//[@java.lang.Deprecated()]を表示します
System.out.println(Arrays.toString(annotations));
リフレクションAPIは、クラスのフィールドを分析し、実行時にその値を変更するためのいくつかのメソッドを提供しています。このセクションでは、一般的に使用されるリフレクション関数のいくつかを見ていきます。
パブリックフィールドを取得する
前のセクションでは、クラスのすべてのパブリックフィールドのリストを取得する方法を見ました。リフレクションAPIは、getField()
メソッドを介してクラスの特定のパブリックフィールドを取得するためのメソッドも提供しています。このメソッドは、指定されたクラス参照、その後のスーパーインターフェース、そしてその後のスーパークラスでフィールドを探します。
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
上記の呼び出しは、ConcreteClassによって実装されるBaseInterfaceからフィールドを返します。フィールドが見つからない場合は、NoSuchFieldExceptionをスローします。
フィールドを宣言しているクラス
フィールドオブジェクトのgetDeclaringClass()
を使用して、フィールドを宣言しているクラスを取得できます。
try {
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
Class<?> fieldClass = field.getDeclaringClass();
System.out.println(fieldClass.getCanonicalName()); //prints com.journaldev.reflection.BaseInterface
} catch (NoSuchFieldException | SecurityException e) {
e.printStackTrace();
}
フィールドの型を取得する
getType()メソッドは、宣言されたフィールドの型のClassオブジェクトを返します。フィールドがプリミティブ型である場合、ラッパークラスのオブジェクトを返します。
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
Class<?> fieldType = field.getType();
System.out.println(fieldType.getCanonicalName()); //prints int
パブリックフィールドの値を取得/設定する
リフレクションを使用してオブジェクトのフィールドの値を取得および設定することができます。
Field field = Class.forName("com.journaldev.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.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//以下のメソッド呼び出しでアクセスチェックを無効にする
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"
リフレクションを使用することで、メソッドに関する情報を取得し、それを呼び出すこともできます。このセクションでは、メソッドを取得する方法、メソッドを呼び出す方法、およびプライベートメソッドにアクセスする方法について学びます。
パブリックメソッドの取得
クラスのパブリックメソッドを取得するには、getMethod()を使用します。メソッド名とメソッドのパラメータータイプを渡す必要があります。メソッドがクラス内で見つからない場合、リフレクションAPIはスーパークラスでメソッドを探します。以下の例では、HashMapのput()メソッドをリフレクションを使用して取得しています。例では、メソッドのパラメータータイプ、メソッドの修飾子、および戻り値のタイプも取得しています。
Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//メソッドのパラメータータイプを取得し、"[class java.lang.Object, class java.lang.Object]"を表示します。
System.out.println(Arrays.toString(method.getParameterTypes()));
//メソッドの戻り値のタイプを取得し、"class java.lang.Object"を返します。voidの場合はvoidのクラス参照
System.out.println(method.getReturnType());
//メソッドの修飾子を取得します。
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"
公開メソッドの呼び出し
メソッドオブジェクトのinvoke()メソッドを使用してメソッドを呼び出すことができます。以下の例では、HashMap上のputメソッドをリフレクションを使用して呼び出しています。
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()を呼び出す方法を示しています。
//非公開メソッドの呼び出し
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
Reflection APIは、クラスのコンストラクタを分析するためのメソッドを提供し、コンストラクタを呼び出すことで新しいインスタンスを作成できます。すでにパブリックコンストラクタを取得する方法を学習しました。
パブリックコンストラクタの取得
オブジェクトのクラス表現でgetConstructor()メソッドを使用して特定のパブリックコンストラクタを取得できます。以下の例では、上記で定義されたConcreteClassのコンストラクタとHashMapの引数なしコンストラクタを取得し、コンストラクタのパラメータ型の配列を取得する方法も示しています。
Constructor > constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//コンストラクタのパラメータを取得する
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.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//コンストラクタのパラメータを取得
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 myMap = (HashMap) hashMapConstructor.newInstance(null);
アノテーションはJava 1.5で導入され、クラス、メソッド、フィールドのメタデータ情報を提供するために使用され、現在ではSpringやHibernateなどのフレームワークで広く使用されています。リフレクションAPIも拡張され、ランタイム時にアノテーションを分析するためのサポートを提供しています。リフレクションAPIを使用して、保持ポリシーがランタイムのアノテーションを分析することができます。すでにアノテーションとリフレクションAPIを使用してアノテーションを解析する方法について詳細なチュートリアルを書いていますので、Javaアノテーションチュートリアルを参照することをおすすめします。これでJavaリフレクションの例のチュートリアルは以上です。このチュートリアルがお役に立ちましたし、JavaリフレクションAPIの重要性も理解していただけたことを願っています。
Source:
https://www.digitalocean.com/community/tutorials/java-reflection-example-tutorial