くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Javaのリフレクションを使ってBeanをPrettyPrintするライブラリをつくってみた(PP4j)

Javaで開発してるときに、大きめなObjectの中身を確認したいなぁーと思い、
きれいに整形してくれるプリティプリントするライブラリを探してみたけど、
なかなかいいのがなかったので、自分で作った時の備忘録

作ったライブラリはこちら。

github.com

SetとかMapとかArarryとかは対応してないけど、いずれ。。MavenCentralにも公開できてないけど、いずれ。。。

表示のされ方はこんな感じ。

こんなBeanに対して、

@Setter
@Getter
public class Sample {
    private String str;
    private List<String> strList;
    private List<Sample> children;
}

こんなインスタンスを作って、

Sample obj = new Sample();
obj.setStr("aaa");
obj.setStrList(Arrays.asList(new String[] {
    "AAA", "BBB", "CCC"
}));

Sample child1 = new Sample();
child1.setStr("child1");
child1.setChildren(Collections.emptyList());

Sample child2 = new Sample();
child2.setStr("child2");

obj.setChildren(Arrays.asList(new Sample[] {
    child1, child2
}));

こんな感じに呼び出すと、

System.out.println(PP4j.pp(obj));

こんな感じにインデントしてくれます。

Sample {
  str = aaa
  strList = [AAA, BBB, CCC]
  children = [
    Sample {
      str = child1
      strList = <null>
      children = []
    }
    Sample {
      str = child2
      strList = <null>
      children = <null>
    }
    Sample {
      str = <null>
      strList = <null>
      children = <null>
    }
  ]
}

使ったリフレクションまとめ

Class<?>系

// java.lang.Classの取得
Class<?> cls = obj.getClass();

// ClassのFQCNの取得
String fqcn = cls.getName();

// Class名の取得
String className = cls.getSimpleName();

// Classに定義されているFieldを取得
Field[] fields = cls.getDeclaredFields();

// cls.getFields()もあるが、こっちはpublicなものしかとれない

Field系

// Fieldに対してアクセス可能に設定。これでprivateフィールドの値も取れる
field.setAccessible(true);

// フィールド名を取得
String fieldName = field.getName();

// オブジェクトのインスタンスから実際のFieldのインスタンスを取得
Object field = field.get(obj);

// FieldのTypeを取得。ListなどGenericsの場合の判定に利用。
Type type = field.getType()

// FieldがList<String>だった場合に、Stringの部分を取得する
if (type == List.class) {
  // Listの場合、総称型を取得
  Type gType = field.getGenericType();

  if (gType instanceof ParameterizedType) {
    // 総称型だった場合、実際の型を取得
    Type[] pType = ((ParameterizedType) gType).getActualTypeArguments();
    // やっとここで、Stringである、pType[0]を取得できる。。。
  }
}

はまったところ。。。

クラス自体が総称型だった場合、実際の型の取得は、要素を確認しないとわからない。

なぜかクラス自体(Class<?>)は、実際の型パラメタの情報を持っていない。。。
なので、実際なんの型なのかは、要素を入る必要がある。

こんな感じだが、要素がnullだったりすると、もはやわからない。。

List<Sample> objList = new ArrayList<>();
objList.add(new Sample());


Object elm = objList.get(0);
Class cls = elm.getClass();

if (cls == String.class) {

}
Filedでも総称の型パラメタを調べるのがめんどう。。。

上記に書いているが、めんどくさい。。
Type自体の型階層がこんな感じ。

java.lang.reflect.Type                  ... インターフェース
  - java.lang.reflect.GenericArrayType  ... サブインタ―フェース
  - java.lang.reflect.ParameterizedType ... サブインタ―フェース
  - java.lang.reflect.TypeVariable<D>   ... サブインタ―フェース
  - java.lang.reflect.WildcardType      ... サブインタ―フェース
  - java.lang.Class<T>                  ... 実装されたクラス

何故作ったか。Commons-LangとかCommons-BeanUtilsとかあるけど。。。

調べてみると、Commonsライブラリにあるらしい。
すこし触ってみると、ObjectのToStringをオーバライドしないといけなく、めんどくさい。。

表示も1行にまとまっていたり、中途半端に改行されたりとなんだかなぁと。。
もう好きに表示できるように作ればいいのかと思いたつ。

Apache Commons Langでの設定

全部のBeanのToStringをオーバライドする
 public String toString() {
   return ToStringBuilder.reflectionToString(this);
 }
表示はこんな感じで1行になる。。。
sample.Sample@7ab2bfe1[str=aaa,strList=[AAA, BBB, CCC],children=[sample.Sample@6438a396[str=child1,strList=<null>,children=[]], sample.Sample@e2144e4[str=child2,strList=<null>,children=<null>]]]

Apache Commons BeanUtilsでの設定

こっちも全部のBeanのToStringを..(ry
public String toString() {
  return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
}
改行はしてくれたけど、インデント。。。
sample.Sample@7ab2bfe1[
  str=aaa
  strList=[AAA, BBB, CCC]
  children=[sample.Sample@6438a396[
  str=child1
  strList=<null>
  children=[]
], sample.Sample@e2144e4[
  str=child2
  strList=<null>
  children=<null>
]]
]

参考にしたサイト様