属性隐藏是字段同名时的静态绑定机制,非多态;子类字段与父类字段独立存在,访问取决于引用的编译时类型,强制转换不改变字段访问目标,唯一安全访问父类字段的方式是子类内使用super.name。

在继承关系中,子类定义与父类同名的字段(非方法)时,会发生属性隐藏(field hiding),而非重写(overriding)。这是 Java 中一个容易被误解的基础机制——它不涉及多态,也不受访问修饰符影响,只要名称相同、类型可不同,子类字段就会“遮蔽”父类字段。而强制类型转换(casting)在此场景下常被误用,以为能“恢复”对父类字段的访问,实则只是改变了引用的编译时类型,运行时对象状态不变。
属性隐藏的本质:静态绑定,非多态行为
属性(字段)没有多态性。当子类声明了与父类同名的字段,两个字段在内存中独立存在。通过父类引用访问时,编译器按引用类型决定读取哪个字段;通过子类引用访问,则优先使用子类字段。
- 父类 String name = "Parent";,子类 String name = "Child"; → 实际创建的是一个对象,内部同时持有两个 name 字段
- Parent p = new Child(); System.out.println(p.name); 输出 "Parent"(看引用类型)
- Child c = new Child(); System.out.println(c.name); 输出 "Child"(看实际变量声明)
强制类型转换无法改变字段访问目标
把子类实例强制转为父类类型(如 (Parent) c),只是让编译器接受该引用作为 Parent 类型使用,并不会删除子类字段、也不会“激活”父类字段的值。字段访问仍由引用的**编译时类型**决定,而非转换动作本身。
- Child c = new Child(); Parent p = (Parent) c; → p.name 仍是父类字段,和直接写 new Parent() 的字段值无关
- 转换不能“回退”对象状态,也不能触发任何字段值同步或覆盖
- 若需访问被隐藏的父类字段值,唯一安全方式是在子类中显式用 super.name 引用(仅限子类内部)
避免隐藏带来的混乱:设计建议
字段隐藏易引发逻辑错误和维护困难,应尽量规避。推荐以下实践:
- 子类不重名定义父类已有的字段;如需扩展,改用新字段名(如 parentName / displayName)
- 用 getter/setter 封装字段访问,子类可重写 getter 方法实现逻辑变化,从而获得真正的多态效果
- 若必须保留同名字段(如兼容旧接口),务必在文档中明确说明隐藏关系,并避免在多态上下文中依赖该字段值
调试与识别隐藏字段的技巧
运行时难以直接观察字段隐藏,但可通过反射或日志辅助验证:
- 用 obj.getClass().getDeclaredFields() 查看子类实际声明的字段列表
- 用 obj.getClass().getSuperclass().getDeclaredFields() 查看父类字段,对比名称是否重复
- 在 IDE 中将鼠标悬停于字段名上,通常会提示 “Shadows field from superclass” 类似警告








