概要
Java書いてる時に、『メソッド名が連続してナンバリングされている場面』 にでくわすことってないですか?ありますよね?わたしはあります。
たとえばこんな感じで、Getterを使ってデータをセットしないといけないときです。
public class Hoge
private int column001;
private int column002;
private int column003;
private int column004;
private int column005;
public void getColumn001(int value){
this.column001 = value;
}
public void getColumn002(int value){
this.column002 = value;
}
public void getColumn003(int value){
this.column003 = value;
}
// 以下略
データベースや外的要因でどうしてもこういう形で保持しないといけないとき、Getter/Setterの数が例のようにこれくらいならいいんですが、100個現れたら面倒です。
そこで、実装の時に「リフレクション(Reflection)」を使うと、メソッドを動的に呼び出すことができるのでとても便利です。
リフレクションってなんぞ
リフレクション(Reflection)とは、プログラムの実行過程の中で、プログラム自身の情報や構造を読み取ったり書き換えたりする技術のことをいいます。
リフレクションを使うと、あるクラスの情報…例えば変数や定数、メソッド名などを取得することができます。
また、取得できる情報の中には、privateなメンバも含まれています。
これを応用することでメソッドを動的に呼び出すことができます。
リフレクションを使おう
リフレクションはいろんな言語でサポートされていますが、この記事ではJavaのリフレクション呼び出し方法を記載します。
サンプルソース
import java.lang.System.*;
/**
* 呼び出されるクラス
**/
public class Hoge{
private int id;
private String dataString;
public void setId(int id){
this.id = id;
System.out.println("Hoge.setIdに" + String.valueOf(id) + "が渡されました。");
}
public void setDataString(String str){
this.dataString = str;
System.out.println("Hoge.setDataStringに" + str + "が渡されました。");
}
public int getId(){
System.out.println("Hoge.getIdが呼び出されました。");
return this.id;
}
public String getDataString(){
System.out.println("Hoge.getDataStringが呼び出されました。");
return this.dataString;
}
// コンストラクタ
public Hoge(){
}
}
package sample;
import java.lang.reflect.Method;
/**
* 呼び出されるクラス
**/
public class Main {
public static void main(String[] args) throws NoSuchMethodException, Exception {
Method method;
Hoge hoge = new Hoge();
// setId を呼び出す
method = hoge.getClass().getMethod("setId",new Class[]{int.class});
method.invoke(hoge, 39);
// setDataString を呼び出す
method = hoge.getClass().getMethod("setDataString",new Class[]{String.class});
method.invoke(hoge, "fuga");
// getId を呼び出す
method = hoge.getClass().getMethod("getId");
int id = Integer.parseInt(String.valueOf(method.invoke(hoge)));
System.out.println(id);
// getDataString を呼び出す
method = hoge.getClass().getMethod("getDataString");
String str = String.valueOf(method.invoke(hoge));
System.out.println(str);
}
}
実行結果
Hoge.setIdに39が渡されました。
Hoge.setDataStringにfugaが渡されました。
Hoge.getIdが呼び出されました。
39
Hoge.getDataStringが呼び出されました。
fuga
ちゅうい
例外
メソッド名と引数はすべて合致している必要があります。
メソッド名が違うとNoSuchMethodException
で怒られますので、リフレクションを使う際には、try-catch か、throws で NoSuchMethodException
を当てるようにしましょう。
※Exception
でもよいのですが、わかりやすさの意味だとNoSuchMethodException
のほうが良い気がします。
メソッド名と引数
Main.java
で Hoge.setId
を呼び出す際にこんな処理を入れています。
method = hoge.getClass().getMethod("setId",new Class[]{int.class});
この、 getMethod
の第2引数に注意してください。
上記の場合だと、Hogeクラスに int型引数のあるgetIdメソッド
がなければNoSuchMethodException
になります。
このため、下記のように書くと実行時エラーとなります。
// 引数指定する際の型が int ではなく Integer である
method = hoge.getClass().getMethod("setId",new Class[]{Integer.class});
プリミティブ型で宣言されていると、そのメソッドを呼び出す際もプリミティブ型.class
とする必要がありますのでご注意ください。
Method.invoke の戻り値
実際にメソッドを実行する時に呼び出す Method.invoke
は、実行した戻り値をすべて Object
型で戻します。
そのため、場合によってはパースする必要があります。
応用
メソッド名を別で保管しておいて動的に呼び出す
メソッド名をデータベースや application.properties
などに格納し、それを読み出して動的にメソッドを実行したいときにはこの小技が使えます。
以下の例は、List<String>
に実行したいメソッド名を格納し、拡張forで連続実行するサンプルです。
Hoge hoge = new Hoge();
List<String> methodList = new ArrayList<>();
methodList.add("sampleMethod1");
methodList.add("sampleMethod2");
for(String str : methodList){
method = hoge.getClass().getMethod(str);
method.invoke(hoge);
}
メソッド名が連続してナンバリングされている場面で動的に呼び出す
今回の冒頭で示したような場面ではこれが強強になります。
とはいえ、たとえば
private int column001;
private int column002;
private int column003;
//private int column004; // 何らかの改修で一部のフィールドがコメントアウトされてる場合
とか、
private int column001;
private int column002;
private int column003;
...
private int column09; // なんかよくわからないけど突然数字部分が0埋め2桁になった
とかされている場合があるかもしれません。
もしこういう形でソースと対峙した場合は、全てを察した顔で中指を立てたあとにおとなしく1つずつメソッドを呼びましょう。
あ、応用1も使えるかもしれませんね?!
以下の例は、Lombokを用いて、getter/setterが自動生成されるモデルクラスのgetterを順番に実行している例です。
ちょっと汚いソースですが、大目に見てやってください…
Lombokってなんぞ?って方は下記の記事を参照すると良いかもしれません。
→ Lombok 使い方メモ
@Data
public class DataForDb{
String id;
String name;
String column001;
String column002;
String column003;
String column004;
String column005;
String column006;
...
...
DataForDb dataForDb = new DataForDb();
Method method;
StringBuilder sb = new StringBuilder();
for(i=1;i++;i<6){
//i=1 のとき、callMethod は "getColumn001"
String callMethod = sb.append("getColumn").append(String.format("%3d", i)).toString();
dataForDb.getClass().getMethod(callMethod);
String retValue = String.valueOf(method.invoke(dataForDb));
}
...
それはそれとして
応用2で紹介した書き方ができるのはいいですが、もしこういうデータ構造で保持しているのだとしたら、モデルクラスのデータ定義そのものを見直したほうが良いかもしれませんね!!!!!