
java 子类声明与父类同名字段时,并非覆盖,而是**字段隐藏(field hiding)**:子类对象在内存中实际包含两份独立的 `x` 字段(`parent.x` 和 `child.x`),分别由各自构造器初始化,通过 `this.x` 和 `super.x` 可明确访问不同副本。
在 Java 中,当子类 C 继承父类 P 并定义了同名字段(如 int x;),这并非“重写”或“覆盖”,而是一种明确的语言特性——字段隐藏(Field Hiding),由《Java 语言规范》(JLS §8.3)明确定义:子类中同名字段会隐藏所有可访问的父类同名字段。关键在于:隐藏 ≠ 合并,它导致同一个对象实例中物理存在两个独立的 x 字段,分别隶属于父类和子类的继承层级。
内存结构:一个对象,两份字段
虽然 new C() 只创建一个对象实例,但该实例的内存布局包含:
- 父类部分(P 的字段):含 P.x,初始值为 1(由 P() 构造器设置);
- 子类扩展部分(C 的字段):含 C.x,初始值为 2(由 C() 构造器设置)。
二者在内存中是完全独立的变量,占据不同偏移地址,互不干扰。
访问规则决定输出差异
你观察到的输出:
立即学习“Java免费学习笔记(深入)”;
c.x: 2 c.getX(): 1
正是字段隐藏行为的直接体现:
- c.x → 编译期根据变量静态类型 C 解析,访问的是 C.x(值为 2);
- c.getX() → getX() 在 P 中定义,其方法体中的 x 指代的是 P 类作用域内的 x(即 P.x),因此返回 1。
✅ 正确访问双字段的示例:
class Child extends Parent {
int x;
Child() {
super(); // 显式调用父构造器(可省略,但强调执行顺序)
this.x = 2; // 初始化 Child.x
}
void printBothXs() {
System.out.println("this.x (Child.x): " + this.x); // 输出 2
System.out.println("super.x (Parent.x): " + super.x); // 输出 1
}
}重要注意事项
- ❌ 无法从外部类直接访问 super.x:c.super.x 是非法语法。super 关键字仅在子类非静态方法/构造器内部有效;
- ⚠️ 避免字段隐藏:这是易出错的设计陷阱。推荐使用不同字段名,或通过 protected 字段 + getter/setter 统一管理;
- ? 构造器执行顺序:new C() 时,JVM 严格按 P() → C() 顺序执行,确保父类字段先初始化,但不会覆盖子类同名字段;
- ? 与方法重写的本质区别:方法可被重写(动态绑定),字段只能被隐藏(静态绑定)。这是理解本问题的核心分水岭。
总结来说,Java 的字段隐藏机制让单个对象能安全容纳多份同名字段,提升继承灵活性,但也要求开发者清晰区分 this. 与 super. 的语义边界——这不是 bug,而是 JLS 精心设计的、可预测的语言特性。










