
本文介绍如何通过递归反射遍历对象及其继承链上的所有字段,筛选出非空且非合成的实例变量,构建仅包含已求值属性的HashMap,彻底解决内部类this$0干扰和父类字段遗漏问题。
本文介绍如何通过递归反射遍历对象及其继承链上的所有字段,筛选出非空且非合成的实例变量,构建仅包含已求值属性的hashmap,彻底解决内部类`this$0`干扰和父类字段遗漏问题。
在Java中,使用getDeclaredFields()仅能获取当前类声明的字段,无法自动包含父类(如MyDocument)中的字段;同时,当子类(如Contract、Summary)被定义为非静态内部类时,JVM会自动注入名为this$0的合成字段(synthetic field),用于保存对外围类实例的隐式引用——这正是输出中出现{this$0=myName.test.generic.MyDocument@6d06d69c}的根本原因。
要正确提取“所有已赋值的实例变量”,需同时解决两个核心问题:
✅ 字段覆盖范围:必须递归向上遍历整个继承链(getSuperclass()),直至Object;
✅ 字段有效性过滤:严格排除合成字段(Field.isSynthetic() == true)、静态字段(Modifier.isStatic(f.getModifiers()))及null值字段。
以下是优化后的getProperties()实现(推荐直接替换原方法):
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
public class MyDocument {
String documentClass; // mandatory
String name; // mandatory
String notes; // NOT mandatory
// ✅ 关键改进:改为 static nested classes(非内部类)
public static class Contract extends MyDocument {
String providerCode;
String providerName;
long activationDate;
long expirationDate;
String expirationNotifyUser;
String expirationNotifyUserType;
Contract(String name, String notes, long activationDate, long expirationDate) {
this.documentClass = "contract";
this.name = name;
this.notes = notes;
this.activationDate = activationDate;
this.expirationDate = expirationDate;
}
}
public static class Summary extends MyDocument {
String origin;
String providerCode;
String providerName;
Summary(String name, String notes) {
this.documentClass = "summary";
this.origin = "area";
this.name = name;
this.notes = notes;
}
}
// ✅ 完整反射实现:支持继承链 + 合成字段过滤
public HashMap<String, Object> getProperties() throws Exception {
HashMap<String, Object> result = new HashMap<>();
Class<?> clazz = this.getClass();
// 递归遍历继承链(含自身)
while (clazz != null && !clazz.equals(Object.class)) {
for (Field f : clazz.getDeclaredFields()) {
// 跳过静态字段、合成字段、不可访问字段
if (Modifier.isStatic(f.getModifiers()) || f.isSynthetic()) {
continue;
}
f.setAccessible(true); // 突破private限制
Object value = f.get(this);
if (value != null) {
result.put(f.getName(), value);
}
}
clazz = clazz.getSuperclass();
}
return result;
}
}⚠️ 重要注意事项:
- 必须将
Contract和Summary声明为static class(即静态嵌套类),否则this$0合成字段必然存在,且isSynthetic()是唯一可靠判断方式(比字符串匹配"this$"更健壮、跨平台兼容);f.setAccessible(true)是必需的,否则无法读取private字段(即使在同一类中,反射默认受访问控制约束);- 若业务允许
null值字段也参与序列化,可移除if (value != null)判断,但需同步调整业务逻辑对null的处理;- 此方案天然支持任意深度继承(如
Contract → PremiumContract → InternationalContract),无需修改代码。
运行示例验证:
OpenJDK 24 Windows x64 官方参考实现(24+36),适合 Windows 环境进行 Java 开发、编译、运行和版本兼容性验证。
立即学习“Java免费学习笔记(深入)”;
public static void main(String[] args) throws Exception {
Summary summ = new MyDocument.Summary("SumName", "SumNotes");
Contract cont = new MyDocument.Contract("ContName", "ContNotes", 12345, 67890);
System.out.println("### Summary:\n" + summ.getProperties());
System.out.println("### Contract:\n" + cont.getProperties());
}输出将精准符合预期:
### Summary:
{documentClass=summary, name=SumName, notes=SumNotes, origin=area}
### Contract:
{documentClass=contract, name=ContName, notes=ContNotes, activationDate=12345, expirationDate=67890}该方案兼顾健壮性、可维护性与跨环境一致性,是Java反射场景下处理多层继承+动态字段提取的标准实践。

















