变量无多态性,访问看编译时类型;方法有多态性,调用看运行时类型。字段仅隐藏不重写,推荐私有字段+重写getter实现多态效果。

多态下访问成员变量时拿到的是父类的值,这不是 bug,而是 Java(及其他类似语言)的设计规则:变量不具有多态性,只有方法才有多态性。关键在于区分“编译时类型”和“运行时类型”。
变量访问看编译时类型
当你用父类引用指向子类对象(如 Animal a = new Dog();),编译器只认得 a 的声明类型(Animal)。所以访问 a.name 时,直接去 Animal 类里找字段 —— 即使 Dog 也定义了同名字段,它也不会被读取。
- 字段在内存中是静态分配的,每个类的字段独立存在,不存在“覆盖”,只有“隐藏”(hiding)
- 子类中声明同名字段,只是新建了一个字段,并不会影响父类字段的值或访问路径
- 若想访问子类字段,必须显式转型:((Dog)a).name,但前提是编译器能确认该转型安全
方法调用才看运行时类型
而 a.speak() 这类方法调用,JVM 在运行时会根据实际对象类型(Dog)查找并执行子类重写的方法。这是动态绑定,也是多态的核心体现。
- 字段没有重写(override),只有方法才有
- 字段可以被子类“隐藏”,但隐藏不触发多态机制
- 如果父类字段是 protected 或包内可见,子类可直接访问父类字段;若子类又声明同名字段,则需用 super.xxx 明确访问父类版本
如何避免混淆?推荐实践
把字段设计为私有(private),通过受保护的 getter/setter 暴露行为 —— 这样你就能在子类中重写 getter 方法,让“获取值”的逻辑具备多态性。
- 例如:父类定义 private String name = "Animal"; + public String getName() { return name; }
- 子类重写 public String getName() { return "Dog"; },调用 a.getName() 就自动走子类逻辑
- 既保持封装,又获得预期的多态效果
检查是否误用了字段隐藏
如果你发现子类写了 String name = "Dog";,却期望 a.name 返回它,那大概率是设计意图与语言机制错位了。IDE 通常会警告 “field hides another field”,这时应优先考虑用 getter + override 替代。
- 字段隐藏易引发维护问题:新增子类时容易忘记同步字段逻辑
- 调试时打印 a.getClass() 可确认运行时类型,但不会改变字段访问结果
- 反射可强行读取子类字段(如 a.getClass().getDeclaredField("name")),但违背封装且不推荐用于常规逻辑

















