Androidでデータを保存するときにはデータを文字列に変換して保存したいが、フィールドの追加・削除などのデータ形式が変更された場合にも対応したいので、シリアライズをそのまま使うと問題がある。 プロトコルバッファを使えば柔軟性があってよさそうだけど、導入までに一手間かかる。

というわけで、リフレクションでフィールドを調べる方法を探ってみた。

要は、調べたいクラスのgetDeclaredFields()を使えばそのクラスのフィールドをすべて取得できる:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

...

  public static void dumpInstanceFields(Object object)
    throws IllegalAccessException {
    for (Field field : object.getClass().getDeclaredFields()) {
      boolean accessible = field.isAccessible();
      try {
        field.setAccessible(true);
        int modifier = field.getModifiers();
        Class<?> type = field.getType();
        String name = field.getName();
        Object value = field.get(object);
        System.out.println(
            modifierString(modifier) + getTypeName(type) + " " + name + " = " + value);
      } finally {
        field.setAccessible(accessible);
      }
    }
  }

  private static String getTypeName(Class type) {
    if (!type.isArray())
      return type.getName();
    return getTypeName(type.getComponentType()) + "[]";
  }

  private static String modifierString(int v) {
    StringBuilder sb = new StringBuilder();
    if (Modifier.isPrivate(v))  sb.append("private ");
    if (Modifier.isPublic(v))  sb.append("public ");
    if (Modifier.isProtected(v))  sb.append("protected ");
    if (Modifier.isStatic(v))  sb.append("static ");
    if (Modifier.isAbstract(v))  sb.append("abstract ");
    if (Modifier.isFinal(v))  sb.append("final ");
    if (Modifier.isInterface(v))  sb.append("interface ");
    if (Modifier.isNative(v))  sb.append("native ");
    if (Modifier.isStrict(v))  sb.append("strict ");
    if (Modifier.isSynchronized(v))  sb.append("synchoronized ");
    if (Modifier.isTransient(v))  sb.append("transient ");
    if (Modifier.isVolatile(v))  sb.append("volatile ");
    return sb.toString();
  }

なんかやたら長いけど基本はシンプルで、

  • Class#getDeclaredFields()ですべてのフィールドの取得、もしくはgetFields()でpublicなフィールドだけ取得できる
  • Field#getType()でそのフィールドのClass、#getName()でフィールド名、#get(オブジェクト)でオブジェクトのフィールドの値を取得できる。
  • #getModifiers()で得られる値はモディファイアのビット和で、Modifier.isXXX()で調べられる。
  • フィールドがpublicじゃない場合、#get()で取得しようとするとIllegalAccessExceptionが発生する。#setAccessible()でtrueを渡すとprivatefinalのフィールドでも無理矢理読み書きできる。
  • リフレクションとは関係ないが、型が配列の場合はそのままtoString()するとまともに表示されない。Class#isArray()で配列かどうか調べて、その場合は#getComponentType()で配列の要素の方を取得できる。

使用の例:

import java.util.List;

public class ReflectionTest {
  static class Foo {
    private int i = 123;
    protected long l = 9876543210L;
    public float f = 1.23f;
    public double d = 1.234567890;
    public static final String s = "foobar";
    public int[] a = { 1, 2, 3 };
    public int[][] a2 = { {1}, {2}, {3} };
    public Foo obj;
    public List<String> list;
  }

  public static void main(String[] args) throws Exception {
    Foo foo = new Foo();
    dumpInstanceFields(foo);
  }
}

実行結果:

private int i = 123
protected long l = 9876543210
public float f = 1.23
public double d = 1.23456789
public static final java.lang.String s = foobar
public int[] a = [I@22998b08
public int[][] a2 = [[I@e76cbf7
public ReflectionTest$Foo obj = null
public java.util.List list = null
  • 型がジェネリックの場合には、Field#getType()だとジェネリック型の型パラメータまではわからない。Field#getGenericType()を使う。