1. 概述
本文将介绍如何通过 Java 反射机制 来检查 Java 应用程序运行时的结构。我们将使用 Java Reflection API 来获取类中的字段信息,包括当前类声明的字段以及继承自父类的字段。
2. 获取类中定义的字段
我们先来看一下如何获取一个类中所有声明的字段(无论其访问修饰符是 public
、protected
还是 private
)。
假设我们有一个 Person
类,它有两个字段:lastName
和 firstName
:
public class Person {
protected String lastName;
private String firstName;
}
这两个字段分别是 protected
和 private
,对外部类来说并不直接可见。但我们可以使用 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()
只会返回 employeeId
和 LABEL
这两个字段,不会包含从 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
字段,因为这些字段的设计本意就是对外隐藏的。我们应优先使用可继承的字段,如 public
和 protected
。
3.2. 过滤 public
和 protected
字段
Java API 中没有现成的方法能直接获取类及其父类中的 public
和 protected
字段。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());
在这个测试中,我们只保留了 public
和 protected
字段。你也可以组合多个修饰符来筛选特定字段。例如,若只想获取 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;
}
这个递归方法会遍历整个类继承链,收集所有 public
和 protected
字段。
我们用一个新类 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());
测试结果符合预期,成功获取了整个继承链中的所有 public
和 protected
字段。
4. 总结
在本文中,我们学习了如何使用 Java Reflection API 获取类中的字段信息:
- ✅ 使用
getDeclaredFields()
获取类中声明的所有字段 - ✅ 使用
getSuperclass()
获取父类字段 - ✅ 使用
Modifier
类过滤public
和protected
字段 - ✅ 编写递归方法处理深层继承结构
反射功能强大,但也容易被滥用。在实际开发中,应谨慎使用反射,尤其是在访问 private
字段时。
完整代码可参考 GitHub 项目地址。