Java中子类“覆盖”父类变量实为隐藏而非重写,编译器依据引用类型(非实际类型)决定访问哪个字段,static和实例字段均可被隐藏,final字段亦可隐藏但不可重赋值,隐藏不改变父类字段本身且无编译提示。

Java中子类“覆盖”父类变量其实是隐藏,不是重写
Java里没有变量的多态性,所谓“子类覆盖父类变量”根本不存在——实际发生的是变量隐藏(hiding)。编译器按引用类型决定访问哪个变量,而不是运行时对象的实际类型。这点和方法重写有本质区别,也是绝大多数困惑的源头。
常见错误现象:Parent p = new Child(); System.out.println(p.field); 输出的是 Parent.field 的值,哪怕 Child.field 赋了不同值;有人误以为这是“重写失败”,其实是根本没触发重写机制。
- 变量查找只看声明类型(即引用类型),不看实际类型
- 只有
static和实例字段能被隐藏,final字段也能被隐藏(但不能重新赋值) - 隐藏不影响父类字段本身,子类字段是独立内存空间,二者无覆盖关系
如何验证变量是否被隐藏而非重写
最直接的办法是用不同引用类型分别访问同一对象,观察输出差异。这是判断隐藏行为最可靠的实操方式。
使用场景:调试继承结构时发现字段值“不一致”,或单元测试中字段表现不符合预期。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
Parent p = new Child(); System.out.println(p.field); // 输出 Parent.fieldChild c = new Child(); System.out.println(c.field); // 输出 Child.field- 如果两个输出相同,说明子类根本没声明同名字段(没隐藏);如果不同,就是隐藏生效
为什么不能靠 @Override 注解防止变量隐藏
@Override 只对方法有效,对字段完全无效。Java 编译器不会报错,也不会警告你写了同名字段——它就静静帮你隐藏了。
容易踩的坑:private 字段虽然不能被子类直接访问,但如果子类声明了同名 public 字段,依然构成隐藏(只是访问路径不同);更隐蔽的是 IDE 自动补全可能诱导你“顺手写个同名字段”,结果破坏了字段语义一致性。
- 字段上加
@Override会编译报错:Method does not override method from its superclass - 字段隐藏无任何编译期提示,纯靠人工审查或静态检查工具(如 ErrorProne 的
FieldHide检查) - Android 开发中尤其危险:View 子类若隐藏父类
mVisibility这类字段,会导致框架逻辑错乱
真正安全的替代方案:用 getter/setter + final 字段
想让子类“定制”字段行为?别碰字段隐藏。把字段设为 private final,提供受控的 protected 或 abstract getter。这样既保留多态性,又杜绝隐藏歧义。
性能影响几乎为零:现代 JVM 对简单 getter 内联非常激进;兼容性上,所有 Java 版本都支持。
-
private final String name;→ 改为private final String name = computeName(); - 子类重写
protected String computeName() { return "child"; } - 避免在构造器中直接读取可被重写的 getter(可能触发子类未初始化字段)
变量隐藏这个机制本身没问题,问题在于它太安静了——不报错、不警告、不参与多态,却悄悄改变语义。只要记住一点:字段看声明类型,方法看实际类型。剩下的,就是把字段藏好,把行为交给方法。

















