
java 子类声明与父类同名的实例字段时,并不会覆盖父类字段,而是**隐藏(hiding)**它;一个子类对象在内存中实际包含两份独立的 `x` 字段——分别属于父类和子类类型,通过 `this.x` 和 `super.x` 可明确区分访问。
在 Java 中,当子类 C 继承父类 P 并定义了同名字段(如 int x;),这并非“重写”或“覆盖”,而是一种字段隐藏(field hiding)行为——这是 Java 语言规范(JLS §8.3)明确定义的特性。关键在于:一个 C 类实例对象在 JVM 堆内存中,确实同时持有两个独立的 x 字段:
- 一份来自父类 P 的继承结构(由 P 的构造器初始化);
- 一份属于子类 C 自身声明的字段(由 C 的构造器初始化)。
二者在内存中物理分离、互不干扰,只是在源码层面因同名而产生访问歧义。
✅ 内存与初始化过程解析
创建 new C() 时,执行流程如下:
- 隐式调用 super() → 进入 P() 构造器 → 将 父类部分的 x 赋值为 1;
- 返回 C() 构造器 → 将 子类自身的 x 赋值为 2;
- 最终对象内存布局等效于:
[C object] ├─ (inherited from P) x = 1 ← 父类字段 └─ (declared in C) x = 2 ← 子类字段
因此,c.x 访问的是当前编译时类型(C)所声明的字段,即子类 x = 2;而 c.getX() 是继承自 P 的方法,其内部 return x; 的 x 是在 P 类作用域内解析的字段,即父类 x = 1 —— 这正是输出 c.x: 2 与 c.getX(): 1 的根本原因。
立即学习“Java免费学习笔记(深入)”;
✅ 显式访问双字段:this.x vs super.x
你可以在子类方法中清晰区分两者:
class C extends P {
int x; // 隐藏父类 x
C() {
x = 2; // 初始化子类 x
}
void showBoth() {
System.out.println("this.x (child): " + this.x); // → 2
System.out.println("super.x (parent): " + super.x); // → 1
}
}⚠️ 注意:super.x 仅可在子类非静态方法/构造器中使用;无法在静态上下文(如 main 方法)或外部类中通过 c.super.x 访问父类被隐藏的字段(JLS §15.11.2)。
❌ 常见误区澄清
- ❌ “只有一个 x 字段” → 错误。字段隐藏 ≠ 字段覆盖,JVM 为每个声明分配独立存储。
- ❌ “getX() 读取的是子类 x” → 错误。getX() 是 P 类的方法,其字节码中 getfield 指令固定指向 P.x。
- ❌ “可通过强制转型访问父类 x” → 无效。(P)c).x 仍返回子类 x(因字段访问由编译时类型决定,而非运行时类型)。
✅ 最佳实践建议
- ? 避免字段隐藏:优先使用不同名称(如 parentX / childX),提升可读性与可维护性;
- ? 优先使用 getter/setter:将字段设为 private,通过受控方法访问,彻底规避隐藏歧义;
- ? 理解多态边界:方法调用遵循动态绑定(运行时类型),字段访问遵循静态绑定(编译时类型)——这是 Java 的核心设计原则。
掌握字段隐藏机制,不仅能解释看似矛盾的输出,更能帮你写出更健壮、意图更清晰的面向对象代码。










