Java 高级教程系列 - 类字段反射

Java Tutorial for Language Adavanced - Field Reflection

Posted by zihengCat on 2019-03-24

类字段反射(Field Reflection)

我们可以使用java.lang.reflect.Field反射取得某个类中的字段(Field)信息。

Java 核心Class类提供的字段类型信息的反射 API 如下所示。

Field[] getFields()
Field[] getDeclaredFields()
Field getField(String name)
Field getDeclaredField(String name)

代码清单:字段(Field)信息反射 API

方法 描述
Field[] getFields() 返回目标类中公开的字段,包括从父类继承的字段。
Field[] getDeclaredFields() 返回目标类中所有的字段,不包括从父类继承的字段。
Field getField(String name) 根据字段名获取公开字段对象。
Field getDeclaredField(String name) 根据字段名获取目标字段对象。

表:字段(Field)反射 API 表

getFields()方法返回类中声明的访问属性为public的字段,包括从父类继承的公开字段,返回值为字段对象数组Field[]

getDeclaredFields()方法返回类中所有声明的字段(publicprivateprotected),但不包括从父类继承的字段,返回值为字段对象数组Field[]

getField(String name)getDeclaredField(String name)按照字段名获取单个字段对象Field,如果目标字段不存在,则抛出NoSuchFieldException异常。

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.util.ArrayList;
class MySuperClass {
    public Integer super_id = -1;
    public String super_name = "Unknown";
}
class MyClass extends MySuperClass {
    public Integer id = -1;
    public String name = "Unknown";
    MyClass(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
    public String toString() {
        return "MyClass: id = " + this.id + ", name = " + this.name;
    }
}
public class Main {
    public static void main(String[] args) {
        Class<MyClass> cls = MyClass.class;
        System.out.println("Declared Fields: ");
        System.out.println(
            getDeclaredFieldsArray(cls)
        );
        System.out.println("Accessible Fields: ");
        System.out.println(
            getFieldsArray(cls)
        );
    }
    public static ArrayList<String> getDeclaredFieldsArray(Class cls) {
        ArrayList<String> list = new ArrayList<String>();
        for (Field field : cls.getDeclaredFields()) {
            try {
                //System.out.println(field);
                list.add(field.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return list;
    }
    public static ArrayList<String> getFieldsArray(Class cls) {
        ArrayList<String> list = new ArrayList<String>();
        for (Field field : cls.getFields()) {
            try {
                //System.out.println(field);
                list.add(field.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return list;
    }
}

代码清单:类字段(Field)反射示例

Java Field

java.lang.reflect.Field类提供关联类字段信息的动态访问。反射的字段可以是类字段(静态)或实例字段。

Field类声明如下所示。

public final class Field
extends AccessibleObject
implements Member

代码清单:Field类声明

对于字段(Field)信息,最主要的操作便是获取与设置,Field类提供了get/set方法,可以便捷地获取/设置字段信息。

Field.get(Object obj)方法用于获取某个类实例目标字段的值,Field.set(Object obj, Object value)方法用于设置某个类实例目标字段的值。

...
    public static void main(String[] args) {
        Class<MyClass> cls = MyClass.class;
        MyClass obj = new MyClass(1, "zihengCat");
        /* Get field value from a class instance */
        try {
            System.out.println(cls.getField("id").get(obj));
            System.out.println(cls.getField("name").get(obj));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        /* Set field value for a class instance */
        System.out.println("Before: " + obj);
        try {
            cls.getField("id").set(obj, 2);
            cls.getField("name").set(obj, "ziheng");
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println("After: " + obj);
    }
...

代码清单:Field类反射 API 运用示例

规避可访问性检查(Accessibility Check)

Java 语言中使用可访问性标识符(publicprivateprotected)限定各类元素(类、接口、方法、字段)的可访问性,但 Java 只在编译阶段进行访问控制检查,不会在*.class文件中留下任何痕迹,通过反射的手段,可以规避可访问性检查。

使用setAccessible(boolean flag)方法(继承自AccessibleObject类),将字段可访问性设置为true,即可以访问标识为私有private的字段,如果不设置可访问性直接访问私有变量,会报出IllegalAccessException异常。

import java.lang.reflect.Field;
class MyClass {
    private String name = "Unknown";
    public MyClass() {
        //...
    }
    public String toString() {
        return "MyClass: name = " + this.name;
    }
}
public class Main {
    public static void main(String[] args) {
        Class<MyClass> cls = MyClass.class;
        try {
            MyClass obj = cls.newInstance();
            Field nameField = cls.getDeclaredField("name");
            /* Bypassing accessibility check */
            nameField.setAccessible(true);
            String nameValue = (String) nameField.get(obj);
            System.out.println("Before: " + nameValue);
            nameField.set(obj, "zihengCat");
            nameValue = (String) nameField.get(obj);
            System.out.println("After: " + nameValue);
        } catch (
            InstantiationException |
            IllegalAccessException |
            NoSuchFieldException |
            SecurityException |
            IllegalArgumentException e) {
            //e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}

代码清单:使用反射规避可访问性检查

参考资料