
本文深入解析 Java 继承中字段(field)不支持重写、仅支持隐藏的机制,阐明为何 A obj = new B() 时 obj.z 访问父类字段而 obj.display() 调用子类方法,并通过内存模型和编译期/运行期行为对比厘清核心逻辑。
本文深入解析 java 继承中字段(field)不支持重写、仅支持隐藏的机制,阐明为何 `a obj = new b()` 时 `obj.z` 访问父类字段而 `obj.display()` 调用子类方法,并通过内存模型和编译期/运行期行为对比厘清核心逻辑。
在 Java 的面向对象模型中,“重写(overriding)”与“隐藏(hiding)”是两个截然不同的概念——它们分别作用于方法和字段,且底层机制、绑定时机与语义含义均存在根本性差异。理解这一区别,是掌握多态行为的关键。
字段:静态绑定 + 隐藏(Hiding),而非重写
当子类定义与父类同名的实例字段(如 class B { int z = 20; }),Java 不会覆盖(override) 父类字段,而是在子类实例中额外声明一个同名字段。此时,父类字段 A.z 和子类字段 B.z 同时存在于同一个 B 对象的内存布局中(位于不同偏移量),互不干扰。
字段访问由引用类型(compile-time type)决定,即静态绑定(static binding)。
A obj = new B(); System.out.println(obj.z); // 编译时认定 obj 是 A 类型 → 访问 A.z → 输出 10
即使实际对象是 B 实例,JVM 也仅根据 obj 的声明类型 A 去查找字段 z,因此读取的是 A 类中定义的 z。
立即学习“Java免费学习笔记(深入)”;
⚠️ 注意:若需访问子类字段,必须显式向下转型:
System.out.println(((B) obj).z); // 输出 20 —— 强制转为 B 类型后访问 B.z
方法:动态绑定 + 重写(Overriding)
非 static、private、final 的实例方法支持重写。调用时采用动态绑定(dynamic dispatch):JVM 在运行时根据实际对象类型(runtime type) 查找并执行对应版本的方法。
obj.display(); // obj 实际是 B 实例 → 触发动态分派 → 执行 B.display()
而 B.display() 内部的 z 是对当前类上下文中的 z 字段的引用——即 this.z 在 B.display() 中解析为 B.z(因为方法属于 B 类,其字节码指令明确指向 B 类的字段槽)。这正是 B's z is 20 的来源。
关键对比表
| 特性 | 字段(Field) | 实例方法(Instance Method) |
|---|---|---|
| 是否可重写 | ❌ 不支持重写,仅支持隐藏 | ✅ 支持重写(满足访问修饰符等条件) |
| 绑定时机 | 编译期(静态绑定) | 运行期(动态绑定) |
| 访问依据 | 引用变量的声明类型 | 实际对象的运行时类型 |
| 内存表现 | 父类与子类字段共存,独立存储 | 子类方法替换父类方法入口(虚方法表) |
实践建议与陷阱规避
- 避免字段同名隐藏:易引发歧义和维护困难。推荐使用 private 字段 + protected getter/setter,由子类通过方法逻辑定制行为。
- 明确区分 @Override 语义:该注解仅对方法有效,对字段无效;若误在字段上使用,编译器会报错。
- 警惕 static 字段/方法:它们同样按引用类型解析(静态绑定),不参与多态,行为与实例字段一致。
归根结底,Java 的设计哲学是:多态性只适用于行为(方法),不适用于状态(字段)。字段代表对象的静态结构,而方法代表对象的动态能力——这种分离保障了类型安全与语义清晰。理解这一原则,便能自然推导出所有看似“矛盾”的继承行为。


















