Javaのリフレクションでクラスの持つフィールドとインスタンスの値を調べる

2013-08-24
blog

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)()を使う。