Java リフレクションを使って階層の深いインスタンスを自動生成する

Java 8で追加されたStreamの複雑な処理を試すにはサンプルデータの生成が重要になります。このページはリフレクションを使ってランダムな数字や文字を詰め込んだインスタンスをクラス定義から自動で生成するプログラムの紹介です。


# はじめに
* ランダムな文字の生成getRandomStringメソッド は「[Java でランダムな文字列を生成する](https://www.delftstack.com/ja/howto/java/random-alphanumeric-string-in-java/)」を参考にしています。
* クラス定義はサンプルです。階層の深さに制限はありません。
* クラスメンバの型はString, int, double, Listに対応しています。
# サンプルコード


▼ クラス定義

```Java
@Data
class Person {
 String name;
 List<Pet> pets;
}

@Data
class Pet {
 List<Cat> cats;
 List<Dog> dogs;
}

@Data
class Cat {
 int id;
 String name;
 double age;
}

@Data
class Dog {
 int id;
 String name;
 double age;
}
```


▼ 階層の深いインスタンスを生成する

```Java
package com.example.marukuma;
// ~~~
// 色々import 
// ~~~

public class Sample {

 public static void main(String[] args) {

  Person obj;
  try {
   obj = method(Person.class, "");
   System.out.println(obj);
  } catch (Exception e) {
   // Auto-generated catch block
   e.printStackTrace();
  }
 }

 private static <T> T method(Class<T> clazz, String indent) throws Exception {

  // 引数なしのデフォルトコンストラクタを呼び出しインスタンスを生成
  T obj = clazz.getConstructor().newInstance();
  
  System.out.println(String.format("%s[%s]", indent, clazz.getName()));
  // リフレクションでクラスの持つフィールドとインスタンスを全て取得
  List<Field> fields = Arrays.asList(clazz.getDeclaredFields());

  final String indent2 = indent + "   ";
  for (Field field : fields) {
   System.out.println(String.format("%s[%s] %s", indent2, field.getType(), field.getName()));
   // ジェネリック型のタイプをチェック
   if (field.getGenericType() instanceof ParameterizedType) {

    ParameterizedType ptype = (ParameterizedType) field.getGenericType();
    Type type = ptype.getActualTypeArguments()[0];
    List list = new ArrayList();

    for (int i : IntStream.rangeClosed(1, 2).boxed().collect(Collectors.toList())) {
     list.add(method(Class.forName(type.getTypeName()), indent2));
    }
    field.set(obj, list);
   } else {
    if (field.getType() == String.class) {
     // ランダムな文字列を取得
     String newString = new String(getRandomString(10));
     try {
      field.set(obj, newString);
     } catch (Exception e) {
      throw new RuntimeException(e);
     }
    } else if (field.getType() == int.class) {
    // ランダムな符号なし整数型を取得
     field.set(obj, new Random().nextInt());
    } else if (field.getType() == double.class) {
     // ランダムな倍精度浮動小数点数型を取得
     field.set(obj, new Random().nextDouble());
    }
   }
  }
  return obj;
 }
```

▼ 結果


```Text
Person(
    name=P4G6Q9AO9K, 
    pets=[
        Pet(
            cats=[
                Cat(
                    id=856636425, 
                    name=PFR3840Q7T, 
                    age=0.448695406586872
                ), 
                Cat(
                    id=-21937606, 
                    name=KD3MJ0VTIE, 
                    age=0.4809853907961903
                    )
                ], 
            dogs=[
                Dog(
                    id=128431623, 
                    name=UAJQ22P8ZF, 
                    age=0.13685394459505584
                    ), 
                Dog(
                    id=334309765, 
                    name=UPHI2ETZX2, 
                    age=0.964195696128925)
                ]
            ), 
        Pet(
            cats=[
                Cat(
                    id=-1625756315, 
                    name=U4ODJXJATD, 
                    age=0.5512031874226584
                    ), 
                Cat(
                    id=-55393982, 
                    name=PRESMNF29W, 
                    age=0.320291117456181
                    )
                ], 
            dogs=[
                Dog(
                    id=-393065222,
                    name=EOKY31SEMO, 
                    age=0.5737964894012947
                    ), 
                Dog(
                    id=-796251753, 
                    name=MIFHKQLKDB, 
                    age=0.6723748789124485
                    )
                ]
            )
        ]
    )
```


# おわりに
Streamの動作確認で階層のあるデータが必要になったので作成してみました。
List＜String＞, List<独自クラス>はバグがあります。改良版はそのうち。。。
# 参考リンク
* [Java でランダムな文字列を生成する](https://www.delftstack.com/ja/howto/java/random-alphanumeric-string-in-java/)