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

java

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

リフレクションとは

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

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

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

<span class="fz-16px">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 +
                '}';
    }
}</span>
<span class="fz-16px">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));
    }

}</span>

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

Student()
true
true
true

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

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

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

<span class="fz-16px">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();
        }
    }
}</span>

実行結果:

<span class="fz-16px">Student()
Student{name='nakada', age=18}
Student(String,name)
Student{name='Tom', age=20}</span>

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

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

<span class="fz-16px">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();
        }
    }
}</span>

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

<span class="fz-16px">Student()
Student{name='Change Name By Reflection', age=18}</span>

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

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

<span class="fz-16px">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();
        }
    }
}</span>

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

<span class="fz-16px">Student()
プライベートなfunctionを呼び出しました:use private method</span>

最後に

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

コメント

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