【Java】列挙型(enum）のリフレクションによる動的メソッド呼び出し

# はじめに
システム改修のお仕事であるが、久しぶりのJavaである。環境はJava7となっている。

とあるテーブルの列でFugaName1～FugaName4となっているのをFugaName1～FugaName50まで拡張することになった。
そこまで増やすなら別テーブルにした方がいいのでは提案はしてみたが、使用状況からするとテーブルを分けるまでもないとのこと。

# 改修前
ファイル出力するようで下記のようなEnumクラスが作成されていた。
2017/06/11にコンストラクタを追記しました。

```Java:Enumクラス
public class Hoge {
    public String no;
    public String fuganame1;
    public String fuganame2;
    public String fuganame3;
    public String fuganame4;
 
    // コンストラクタ　
    public Hoge(String _no, HashMap<String, String> resultList) {
        no = _no;
        fuganame1 = resultList.get(dataParams.FugaName1.getColNamePhysical());   
        fuganame2 = resultList.get(dataParams.FugaName2.getColNamePhysical());   
        fuganame3 = resultList.get(dataParams.FugaName3.getColNamePhysical());   
        fuganame4 = resultList.get(dataParams.FugaName4.getColNamePhysical());   
    }

    public enum dataParams{
          No         ("no"        , "No"         , 0)
        , FugaName1  ("fuganame1" , "Fuga Name1" , 1)
        , FugaName2  ("fuganame2" , "Fuga Name2" , 2)
        , FugaName3  ("fuganame3" , "Fuga Name3" , 3)
        , FugaName4  ("fuganame4" , "Fuga Name4" , 4);

        private final String colNamePhysical;   //物理名
        private final String colNameLogical;    //論理名
        private final int colIdx;

        private dataParams( String physical , String logical, int index) {
            colNamePhysical = physical;
            colNameLogical = logical;
            colIdx = index;
        }

        public String getColNamePhysical() {
            return colNamePhysical;
        }
        public String getColNameLogical() {
            return colNameLogical;
        }
        public int getColIdx() {
            return colIdx;
        }
    }
 }
```

4つ程度なのでベタに書かれている。

```Java:使用箇所
String fugaName1 = (String)Hoge.dataParams.FugaName1.getColNamePhysical();
addMap.put(fmColumnName, fugaName1);
resultList.add(addMap);
String fugaName2 = (String)Hoge.dataParams.FugaName2.getColNamePhysical();
addMap.put(fmColumnName, fugaName2);
resultList.add(addMap);
String fugaName3 = (String)Hoge.dataParams.FugaName3.getColNamePhysical();
addMap.put(fmColumnName, fugaName3);
resultList.add(addMap);
String fugaName4 = (String)Hoge.dataParams.FugaName4.getColNamePhysical();
addMap.put(fmColumnName, fugaName4);
resultList.add(addMap);
```
# 実験
まだ4つならベタでも許容範囲であるが、さすがに50個も書くというのはプログラマーとしてイケてないと思うのと、他にも同じようなことをしているのが複数あるようなので、今後のことを考えてリフレクションに挑戦してみた。
リフレクションを使うのは初めてだったので、確認用のプログラムを作る。

```Java:実験
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {

    public static void main(String []args) {

        Enum<?> clz = Enum.valueOf(Hoge.dataParams.class, "FugaName2");
        Object[] consts = clz.getClass().getEnumConstants();
        Class<?> sub = consts[0].getClass();
        Method mth = sub.getDeclaredMethod("getColNameLogical"); //getColNamePhysical");
        String val = (String) mth.invoke(consts[clz.ordinal()]);

        System.out.println(val); // 結果: Fuga Name2
    }
}

```

# 改修後
Enumクラスについては、とりあえずベタに50まで増やした。
使用箇所についてはリフレクションにて対応した。

```Java:使用箇所
Class<?> clz = Hoge.dataParams.class;
Object[] consts = clz.getEnumConstants();
Class<?> sub = consts[0].getClass();

try {
    Method mth = sub.getDeclaredMethod("getColNamePhysical");
    for(int i=1; i<=50; i++){
        Enum<?> v = Enum.valueOf(Hoge.dataParams.class, String.format("FugaName%d", i));
        String fugaName = (String) mth.invoke(consts[v.ordinal()]);
        addMap.put(fmColumnName, fugaName);
        resultList.add(addMap);
    }
} catch (NoSuchMethodException|SecurityException|IllegalAccessException|InvocationTargetException ex) {
    throw ex;
}
```
## 追記 2017/06/11
saka1029さんからコメントを頂き、リフレクションしない方法を習得しました。

```Java:使用箇所
for (Hoge.dataParams v : Hoge.dataParams.values()) {
    if (!v.toString().matches("FugaName\\d+"))
        continue;
    String fugaName = v.getColNamePhysical();
    addMap.put(fmColumnName, fugaName);
    resultList.add(addMap);
}
```
コンストラクタのところは、getField(フィールド名)を使いフィールド変数を取得しています。
その場合、throws IllegalArgumentException, IllegalAccessException が追加されます。

参照：[getFields と getDeclaredFields の違い](http://did2memo.net/2013/09/10/java-reflection-how-to-display-all-java-field/)

```Java:Enumクラス
    // コンストラクタ　
    public Hoge(String _no, HashMap<String, String> resultList) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
        no = _no;

        // 下記と同じことをFugaName1-50まで行う
        // fuganame1 = resultList.get(dataParams.FugaName1.getColNamePhysical());
        for (Hoge.dataParams v : Hoge.dataParams.values()) {
            String name = v.toString();
            if (!name.matches("FugaName\\d+"))
                continue;
            Field field = this.getClass().getField(name.toLowerCase());
            field.set(this, resultList.get(v.getColNamePhysical()));
        }
    }
```

※リフレクションでフィールドを取得した場合、フィールドの順番は仕様として決まっていないとのこと。
参照：[Java リフレクションで取得するフィールドの順番](http://ksoichiro.blogspot.jp/2011/05/java.html)
>Java API仕様を見ると、確かに順序は決まっていないという記述がありました。
>Class#getFields()
>
>この Class オブジェクトが表すクラスまたはインタフェースのすべてのアクセス可能な public フィールドをリフレクトする、Field オブジェクトを保持している配列を返します。返された配列内の要素は、ソートされていたり、特定の順序になっていたりすることはありません。

Javaの標準的な機能ではフィールドの定義順でフィールド情報を取得することはできないので、アノテーションを使うことで順序を規定させる。
参照：[アノテーションの値順でフィールドを出力](http://java-beginner.com/practice-print-fields-in-order/)

# 最後に
本格的にJavaで改修作業するのは初めてなので、本当はもっといい方法があるのかも知れない。

# 参照
* [How to use Java reflection when the enum type is a Class? - stackoverflow](https://stackoverflow.com/questions/140537/how-to-use-java-reflection-when-the-enum-type-is-a-class)
* [Class型と文字列から対応するenum値を取得するには？ - stackoverflow](
https://ja.stackoverflow.com/questions/8197/class%E5%9E%8B%E3%81%A8%E6%96%87%E5%AD%97%E5%88%97%E3%81%8B%E3%82%89%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8Benum%E5%80%A4%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF)
* [[Java]staticメソッドのリフレクション](http://d.hatena.ne.jp/mtoyoshi/20080922/1222139882)