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

2013-08-24

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

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

例えばなにか適当なクラスがあったとして:

import java.util.List;

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;
}

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

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

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

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);
}
}
}

型名は配列を特別に判定、 getModifiers で得られた値は個別に判定:

  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()で配列の要素の方を取得できる。

実行結果:

$ javac ReflectionTest.java && java ReflectionTest
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@70dea4e
public int[][] a2 = [[I@5c647e05
public Foo obj = null
public java.util.List list = null
  • 型がジェネリックの場合には、Field#getType()だとジェネリック型の型パラメータまではわからない。Field#getGenericType()を使う。