1. 概述

本文将介绍如何通过 Java 反射机制 来检查 Java 应用程序运行时的结构。我们将使用 Java Reflection API 来获取类中的字段信息,包括当前类声明的字段以及继承自父类的字段。

2. 获取类中定义的字段

我们先来看一下如何获取一个类中所有声明的字段(无论其访问修饰符是 publicprotected 还是 private)。

假设我们有一个 Person 类,它有两个字段:lastNamefirstName

public class Person {
    protected String lastName;
    private String firstName;
}

这两个字段分别是 protectedprivate,对外部类来说并不直接可见。但我们可以使用 Class::getDeclaredFields 方法来获取所有声明的字段。该方法返回一个 Field 对象数组,包含了字段的所有元数据:

List<Field> allFields = Arrays.asList(Person.class.getDeclaredFields());

assertEquals(2, allFields.size());
Field lastName = allFields.stream()
    .filter(field -> field.getName().equals(LAST_NAME_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found"));
assertEquals(String.class, lastName.getType());
Field firstName = allFields.stream()
    .filter(field -> field.getName().equals(FIRST_NAME_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found"));
assertEquals(String.class, firstName.getType());

测试时,我们通过字段名过滤返回的数组,并验证字段类型是否正确。

3. 获取继承字段

接下来我们看看如何获取类从父类继承来的字段。

我们让 Employee 类继承 Person 类,并新增两个 public 字段:

public class Employee extends Person {
    public static final String LABEL = "employee";
    public int employeeId;
}

3.1. 获取简单继承结构中的字段

调用 Employee.class.getDeclaredFields() 只会返回 employeeIdLABEL 这两个字段,不会包含从 Person 继承来的字段。如果我们希望获取父类中的字段,可以分别调用 getDeclaredFields() 方法获取两个类的字段并合并。但如果我们不想显式引用父类名称,可以使用 Class::getSuperclass() 方法。

getSuperclass() 方法能返回当前类的父类,而无需显式指定父类名称。然后我们可以获取父类的字段,并将它们与子类字段合并:

List<Field> personFields = Arrays.asList(Employee.class.getSuperclass().getDeclaredFields());
List<Field> employeeFields = Arrays.asList(Employee.class.getDeclaredFields());
List<Field> allFields = Stream.concat(personFields.stream(), employeeFields.stream())
    .collect(Collectors.toList());

assertEquals(4, allFields.size());
Field lastNameField = allFields.stream()
    .filter(field -> field.getName().equals(LAST_NAME_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found"));
assertEquals(String.class, lastNameField.getType());
Field firstNameField = allFields.stream()
    .filter(field -> field.getName().equals(FIRST_NAME_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found"));
assertEquals(String.class, firstNameField.getType());
Field employeeIdField = allFields.stream()
    .filter(field -> field.getName().equals(EMPLOYEE_ID_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found"));
assertEquals(employeeIdField.getType(), int.class);
Field employeeTypeField = allFields.stream()
    .filter(field -> field.getName().equals(EMPLOYEE_TYPE_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found"));
assertEquals(String.class, employeeTypeField.getType());

从测试结果可以看出,我们成功获取了 Person 的两个字段和 Employee 的两个字段。

⚠️ 反射虽然强大,但要谨慎使用。比如,不建议直接访问类的 private 字段,因为这些字段的设计本意就是对外隐藏的。我们应优先使用可继承的字段,如 publicprotected

3.2. 过滤 publicprotected 字段

Java API 中没有现成的方法能直接获取类及其父类中的 publicprotected 字段。Class::getFields() 方法只能返回所有 public 字段(包括继承的),但不包括 protected 字段。因此我们需要使用 getDeclaredFields() 并通过 Field::getModifiers() 方法手动过滤。

getModifiers() 返回一个 int 值,表示字段的访问修饰符。这个值是 2^0 到 2^7 的组合。例如,public 是 2^0,static 是 2^3。因此一个 public static 字段的修饰符值是 9。

我们可以使用 Modifier 类提供的辅助方法,比如 Modifier.isPublic()Modifier.isProtected(),来筛选出我们想要的字段:

List<Field> personFields = Arrays.stream(Employee.class.getSuperclass().getDeclaredFields())
  .filter(f -> Modifier.isPublic(f.getModifiers()) || Modifier.isProtected(f.getModifiers()))
  .collect(Collectors.toList());

assertEquals(1, personFields.size());

Field personField = personFields.stream()
    .filter(field -> field.getName().equals(LAST_NAME_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found"));
assertEquals(String.class, personField.getType());

在这个测试中,我们只保留了 publicprotected 字段。你也可以组合多个修饰符来筛选特定字段。例如,若只想获取 public static final 字段:

List<Field> publicStaticField = Arrays.stream(Employee.class.getDeclaredFields())
    .filter(field -> Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) &&
        Modifier.isFinal(field.getModifiers()))
    .collect(Collectors.toList());

assertEquals(1, publicStaticField.size());
Field employeeTypeField = publicStaticField.get(0);
assertEquals(EMPLOYEE_TYPE_FIELD, employeeTypeField.getName());

3.3. 获取深层继承结构中的字段

前面的例子都是基于单层继承结构。如果类继承链更深,比如 MonthEmployee extends Employee,而 Employee extends Person,我们该如何获取整个继承链中的字段?

为此,我们可以编写一个递归方法,自动遍历整个类继承结构,并返回所有符合条件的字段:

List<Field> getAllFields(Class clazz) {
    if (clazz == null) {
        return Collections.emptyList();
    }

    List<Field> result = new ArrayList<>(getAllFields(clazz.getSuperclass()));
    List<Field> filteredFields = Arrays.stream(clazz.getDeclaredFields())
      .filter(f -> Modifier.isPublic(f.getModifiers()) || Modifier.isProtected(f.getModifiers()))
      .collect(Collectors.toList());
    result.addAll(filteredFields);
    return result;
}

这个递归方法会遍历整个类继承链,收集所有 publicprotected 字段。

我们用一个新类 MonthEmployee 来测试:

public class MonthEmployee extends Employee {
    protected double reward;
}

该类新增了一个 reward 字段。根据继承链,我们期望获取到如下字段:

  • Person::lastName
  • Employee::employeeId
  • MonthEmployee::reward

调用 getAllFields() 方法:

List<Field> allFields = getAllFields(MonthEmployee.class);

assertEquals(4, allFields.size());

assertFalse(allFields.stream().anyMatch(field -> field.getName().equals(FIRST_NAME_FIELD)));
assertEquals(String.class, allFields.stream().filter(field -> field.getName().equals(LAST_NAME_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found")).getType());
assertEquals(int.class, allFields.stream().filter(field -> field.getName().equals(EMPLOYEE_ID_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found")).getType());
assertEquals(double.class, allFields.stream().filter(field -> field.getName().equals(MONTH_EMPLOYEE_REWARD_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found")).getType());
assertEquals(String.class, allFields.stream().filter(field -> field.getName().equals(EMPLOYEE_TYPE_FIELD))
    .findFirst().orElseThrow(() -> new RuntimeException("Field not found")).getType());

测试结果符合预期,成功获取了整个继承链中的所有 publicprotected 字段。

4. 总结

在本文中,我们学习了如何使用 Java Reflection API 获取类中的字段信息:

  • ✅ 使用 getDeclaredFields() 获取类中声明的所有字段
  • ✅ 使用 getSuperclass() 获取父类字段
  • ✅ 使用 Modifier 类过滤 publicprotected 字段
  • ✅ 编写递归方法处理深层继承结构

反射功能强大,但也容易被滥用。在实际开发中,应谨慎使用反射,尤其是在访问 private 字段时。

完整代码可参考 GitHub 项目地址


原始标题:Retrieve Fields from a Java Class Using Reflection | Baeldung