【Java入門】リフレクションでメソッドの実行、フィールドの変更

リフレクションはクラスやメソッドを変数として扱うことができて、異なるクラスやメソッドを1つのプログラムで実行できるので便利です。

プログラムをテストする時などでよく使われたりします。

この記事ではリフレクションについて、

  • リフレクションとは
  • リフレクションの使い方
  • クラスの取得
  • インスタンスの生成
  • メソッドの取得と実行
  • privateフィールドの参照と変更
  • リフレクションの危険性について

など、リフレクションの基礎から具体的な使い方についても解説していきます。

なお、Javaの記事については、こちらにまとめています。

目次

リフレクションとは

リフレクションとは、クラス名やメソッド名を指定して「クラスを動的に実行する」ことです。

「クラスを動的に実行する」とはクラス名やメソッド名を文字列の変数として指定することで、クラス名やメソッド名を変更してもプログラムが実行できるようにしています。

クラス名やメソッド名を変数として変更してもプログラムが実行できるので、プログラムのテストなどで使用されますちなみに、コマンドラインでJavaのプログラムを実行する際に

java クラス名

と記述しますが、これもクラス名を変更してプログラムが実行できるのでリフレクションを行っていることになります。

リフレクションの使い方

リフレクションの使い方について、クラス、インスタンス、メソッド、フィールドの順にみていきましょう。

クラスの取得

クラスを取得するためにはClassクラスを使用しますClassクラスの使い方は以下のようにいくつか方法があります。

// 方法1
Class<クラス名> オブジェクト名 = クラス名.class;

// 方法2
クラス名 オブジェクト名1 = new クラス名();
Class<? extends クラス名> オブジェクト名2 = オブジェクト名1.getClass();

// 方法3
Class<?> オブジェクト名 = Class.forName("クラス名");

foNameメソッドを使用する場合は、ClassNotFoundExceptionの例外処理を記述する必要があります。

なお、リフレクションに関連する例外の共通クラスReflectiveOperationExceptionで処理することも可能です。

インスタンスの生成

インスタンスを生成する記述は以下のとおりになります。

Class<クラス名> オブジェクト名1 = クラス名.class;
Object オブジェクト名2 = オブジェクト名1.newInstance();

これは、

クラス名 オブジェクト名2 = new クラス名();

に相当します。

メソッドの取得と実行

メソッドを取得するにはMethodクラスのオブジェクトにgetMethodメソッドの戻り値を格納します。

Method オブジェクト名 = Classクラスのオブジェクト名.getMethod(メソッド名, 引数1の型名.class, 引数2の型名.class, ・・・);

getMethodメソッドの第2引数以降はClassクラスの可変長引数になります。

メソッドを実行するためにはinvokeメソッドを実行します。

Methodクラスのオブジェクト.invoke(生成したインスタンス, 引数1, 引数2, ・・・)

invokeメソッドの第2引数以降は可変長引数になります。

サンプルコードで確認しましょう。

import java.lang.reflect.Method;

class TestClass {
    private String str;
    
    public void setStr(String str) {
        this.str = str;
    }
    
    public String getStr() {
        return str;
    }
} 

public class Main {
 
    public static void main(String[] args) {
        // クラス、メソッドを文字列で指定
        String strClass = "TestClass";
        String strMethod1 = "setStr";
        String strMethod2 = "getStr";
        
        // テスト用の変数
        String str1 = "test";
        
        // リフレクション
        try {
            // クラスの取得
            Class<?> c = Class.forName(strClass);
            // インスタンスの生成
            Object myObj = c.newInstance();
            
            // メソッド(setStr)の取得
            Method m = c.getMethod(strMethod1, String.class);
            // メソッド(setStr)の実行
            m.invoke(myObj, str1);
            
            // メソッド(getStr)の取得
            m = c.getMethod(strMethod2);
            // メソッド(getStr)の実行
            System.out.println(m.invoke(myObj)); 
        } catch(ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }

}

実行結果:

test

このサンプルコードでは、クラス名とメソッド名をそれぞれString型の変数に格納しています。

クラス名やメソッド名を格納した変数をforNameメソッド、getMethodメソッド、invokeメソッドなどのメソッドの引数に指定して実行しています。

privateフィールドの参照と変更

ここではprivateフィールドの参照と変更の方法について見ていきましょう以下のサンプルコードで確認しましょう。

import java.lang.reflect.Field;
import java.lang.reflect.Method;

class TestClass {
    private String str;
    
    public void setStr(String str) {
        this.str = str;
    }
    
    public String getStr() {
        return str;
    }
} 

public class Main {
 
    public static void main(String[] args) {
        // クラス、メソッド、フィールドを文字列で指定
        String strClass = "TestClass";
        String strMethod1 = "setStr";
        String strMethod2 = "getStr";
        String strField = "str";
        
        // テスト用の変数
        String str1 = "test";
        String str2 = "テスト";
        
        // リフレクション
        try {
            // クラスの取得
            Class<?> c = Class.forName(strClass);
            // インスタンスの生成
            Object myObj = c.newInstance();
            
            // メソッド(setStr)の取得
            Method m = c.getMethod(strMethod1, String.class);
            // メソッド(setStr)の実行
            m.invoke(myObj, str1);
            
            // フィールド(str)の取得
            Field f = c.getDeclaredField(strField);
            f.setAccessible(true);
            System.out.println(f.get(myObj)); 
            
            // フィールド(str)の変更
            f.set(myObj, str2);
            System.out.println(f.get(myObj));
        } catch(ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }

}

実行結果:

test
テスト

このサンプルコードでは、まずstrMethod1で指定されたメソッドでフィールドに値を書き込んでいます。

その値をFieldクラスのオブジェクトfから呼び出されたgetメソッドを使って取得しています。また、その値をオブジェクトfから呼び出されたsetメソッドを使って変更しています。

リフレクションの危険性について

リフレクションを使用する場合は以下のことに注意しましょう。

クラス設計を破壊する危険性

これまでお伝えしたように、アクセス修飾子privateで「カプセル化」した値を、直接取得・変更することができました。

このように直接操作することを可能にしてしまうため、リフレクションには既存のクラス設計を破壊してしまう危険性があります。

コンパイル時にエラーの検出対象外

以前はリフレクションを使用した部分のコードは、コンパイル時のエラー検出対象外でエラーを検出させることができませんでした。

リフレクション部分のエラーはプログラム実行時にのみ検出するしかありませんでした。

しかし、Java7以降ではReflectiveOperationExceptionでリフレクションに関連する例外の共通クラスが定義されたため、例外処理を記述できるようになりました。

まとめ

ここでは、リフレクションについて説明しました。

リフレクションを使って記述すれば、後はクラス名やメソッド名など変数に代入する値を変更するだけで異なるクラスやメソッドを実行することができます。プログラムをテストする場合などで使用すると便利です。

使いこなすことができるように、この記事を何度も参考にして下さいね!

この記事を書いた人

熊本在住のフリープログラマ兼ライターです。C/C++/C#、Java、Python、HTML/CSS、PHPを使ってプログラミングをしています。専門は画像処理で最近は機械学習、ディープラーニングにはまっています。幅広くやってきた経験を活かしてポイントをわかりやすくお伝えしようと思います。
お問合せはこちらでも受け付けています。
info@sss-lab.com

目次