访问字段
对任意的一个Object实例,只要获取了它的Class,就可以获取它的一切信息。

Class类提供了以下几个方法来获取字段信息:
Field getField(name):根据字段名获取某个public的field(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
Field[] getFields():获取所有public的field(包括父类)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

注意:getFields()不能读取private字段,getDeclaredFields不能读取父类的字段。

通过函数返回的Field对象包含了一个字段的所有信息,可通过以下函数取得:

getName():返回字段名称,例如,"name";
getType():返回字段类型,也是一个Class实例,例如,String.class;
getModifiers():返回字段的修饰符,它是一个int类型的数据,不同的bit(位)表示不同的含义。

以String类的value字段为例,它的定义是:

public final class String {
    private final byte[] value;
}

用反射获取该字段的信息,代码及结果如下:

Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型
int m = f.getModifiers();
Modifier.isFinal(m);  // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m);  // false

获取字段值

利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。
如:

Object p = new Person("Xiao Ming");
//获取Class实例
Class c = p.getClass();
//获取private字段,设为可访问
Field f = c.getDeclaredField("name");
f.setAccessible(true);
//用Field.get(Object)获取指定字段的值
Object value = f.get(p);
System.out.println(value); // "Xiao Ming"

正常情况下,无法访问另一个类的private字段。
调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。

反射更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。

setAccessible(true)可能会失败。
如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。
例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。

设置字段值
通过Field实例既然可以获取到指定实例的字段值,自然也可以设置字段的值。

设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。
示例:

Person p = new Person("Xiao Ming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
f.set(p, "Xiao Hong");

运行上述代码,打印的name字段从Xiao Ming变成了Xiao Hong,说明通过反射可以直接修改字段的值。

同样的,修改非public字段,需要首先调用setAccessible(true)。

小结
Java的反射API提供的Field类封装了字段的所有信息:

通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields();

通过Field实例可以获取字段信息:getName(),getType(),getModifiers();

通过Field实例可以使用Field.set(Object, Object)来读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。

通过反射读写字段是一种非常规方法,它会破坏对象的封装。