【Java】リフレクション入門

java

 Springフレームワークのソースコードを読む際、Javaのリフレクション仕組みを多く利用されていることがわかりました。実際の業務中にあまり使われてないかもしれないが、理解できると非常に助かります。

リフレクションとは

 リフレクションとは、クラス名・メソッド名・変数名などを文字列として指定して動的に実行するためのJava標準APIです。
 リフレクションを利用すると、任意のクラスについて、このクラスのすべてのプロパティとメソッドを取得すること、呼び出すことができます。もちろん、取得できるので、型情報の一部を変更することもできます。
 リフレクションを利用することで、例えば private なメソッドや変数であってもアクセスして使うことが可能となります。

リフレクションを利用してClassオブジェクトを取得する

 リフレクションを利用してClassオブジェクトを取得するには、3つの方法があります。
 ・Class クラスの forName メソッドを利用する。
 ・クラス名.classを利用する。
 ・インスタンスのgetclassメソッドを呼び出して利用する。
 
 では、この3つの方法を同じ対象クラスを取得するソースをみてみましょう。

package javaSample;

class Student{
    //プライベート属性のname
    private String name = "nakada";
    //パブリック属性のage
    public int age = 18;
    //引数がないコンストラクタ
    public Student(){
        System.out.println("Student()");
    }
    //引数があるコンストラクタ
    private Student(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Student(String,name)");
    }

    private void eat(){
        System.out.println("i am eating");
    }

    public void sleep(){
        System.out.println("i am sleeping");
    }

    private void function(String str) {
        System.out.println("プライベートなfunctionを呼び出しました:"+str);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package javaSample;

public class reflection {

	public static void main(String[] args) {
	    //1.Class クラスの forName メソッドを利用する
	    Student student1 = new Student();
	    Class<?> c1 = student1.getClass();
	    //2、クラス名.classを利用する
	    Class<?> c2 = Student.class;
	    //3. Class クラスの forName メソッドを利用する
	    Class<?> c3 = null;
	    try {
                //★完全修飾名が必要です!Studentだけはエラーになる!
	        c3 = Class.forName("javaSample.Student");
	    } catch (ClassNotFoundException e) {
	        throw new RuntimeException(e);
	    }
	    System.out.println(c1.equals(c2));
	    System.out.println(c1.equals(c3));
	    System.out.println(c2.equals(c3));
	}

}

実行結果:3つの方法は同じ対象を取得できました。

Student()
true
true
true

インフレクションを利用してインスタンスを作成する

 リフレクションを使用してClassオブジェクトのコンストラクターを取得し、インスタンスを作成するには、以下の手順となります。

 ①classオブジェクトを取得します。
 ②コンストラクターを取得します。
 ③プライベートなコンストラクターを取得する場合は、コンストラクターの setAccessible メソッドを介してアクセスを有効にする必要があります。
 ④コンストラクターで newInstance メソッドを呼び出します。

package javaSample;

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

public class reflection {

	public static void main(String[] args) {
    
	    //Classオブジェクトを取得する
	    Class<?> c = Student.class;
	   
	    try {
	    	//引数なしのコンストラクタを利用してインスタンスを作成する
			Student studentDefault, student;
			studentDefault = (Student) c.getDeclaredConstructor().newInstance();
			System.out.println(studentDefault);
			
			//引数ありのコンストラクタを利用してインスタンスを作成する
			Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
			//プライベートなコンストラクタをアクセスするため、権限の変更が必要
			constructor.setAccessible(true);
			student = (Student)constructor.newInstance("Tom", 20);
			System.out.println(student);
			
		} catch (InstantiationException | IllegalAccessException | IllegalArgumentException 
				| InvocationTargetException | NoSuchMethodException | SecurityException e) {
			e.printStackTrace();
		}
	}
}

実行結果:

Student()
Student{name='nakada', age=18}
Student(String,name)
Student{name='Tom', age=20}

リフレクションを利用してフィールドを取得する

 リフレクションを利用してフィールド(privateも!)を取得するだけではなく、フィールドの値を修正することも可能です。

package javaSample;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class reflection {

	public static void main(String[] args) {


		Class<?> c = Student.class;
		try {
			Student student;
			student = (Student) c.getDeclaredConstructor().newInstance();
			//プライベートなフィールドnameを取得する
			Field field =  c.getDeclaredField("name");
			//プライベートなフィールドをアクセスするため、権限の変更が必要
			field.setAccessible(true);
			//フィールドの値を修正する
			field.set(student, "Change Name By Reflection");
			System.out.println(student);
			} catch (InstantiationException | IllegalAccessException | IllegalArgumentException 
				| InvocationTargetException | NoSuchMethodException | NoSuchFieldException | SecurityException e) {
			e.printStackTrace();
		}
	}
}

実行結果:privateフィールドの値を修正できました。

Student()
Student{name='Change Name By Reflection', age=18}

リフレクションを利用してメソッドを取得する

 リフレクションを利用してメソッドを取得し、invoke()で呼び出すことができます。
 もちろんですが、privateメソッドも取得できます。

package javaSample;

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

public class reflection {

	public static void main(String[] args) {

	    Class<?> c = Student.class;
	    try {
	    	Student student;
	    	student = (Student) c.getDeclaredConstructor().newInstance();
	        //プライベートなメソッドfunctionを取得する
	    	Method method =  c.getDeclaredMethod("function", String.class);
	        //プライベートなメソッドをアクセスするため、権限の変更が必要
	    	method.setAccessible(true);
	        //メソッドを実行させる
	    	method.invoke(student, "use private method");
	    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException 
				| InvocationTargetException | NoSuchMethodException | SecurityException e) {
			e.printStackTrace();
		}
	}
}

実行結果:privateなメソッドを呼び出して実行しました。

Student()
プライベートなfunctionを呼び出しました:use private method

最後に

 利用してを理解すると、フレームワークのソースコードを読む際に大変助かりますが、実際の業務中にはリフレクションをできるだけ使用を避けましょう。
 理解しにくくなって、メンテナンスが大変になるのもありますが、ソースの実行効率も悪くなります。

コメント

タイトルとURLをコピーしました