
Java 中字段访问由引用类型决定,方法调用由实际对象类型决定;obj.z 读取的是 A 类声明的字段(字段隐藏),而 obj.display() 执行的是 B 类重写的方法(动态绑定),因此输出结果看似矛盾实则严格遵循语言规范。
java 中字段不支持多态,而方法支持:理解变量隐藏与方法重写的本质区别
在 Java 的面向对象机制中,“继承”对字段(field)和方法(method)的处理方式存在根本性差异——这正是你观察到 obj.z 和 obj.display() 行为不一致的核心原因。
字段:静态绑定(隐藏,而非重写)
Java 不允许字段被重写(override)。当你在子类 B 中声明同名字段 int z = 20;,它并不会覆盖(override)父类 A 中的 int z = 10;,而是隐藏(hide)了父类字段——两者在内存中同时存在,属于不同作用域的独立变量。
- A obj = new B(); 中,obj 是类型为 A 的引用,指向 B 实例。
- 访问 obj.z 时,编译器仅根据引用类型 A 决定访问哪个字段 → 读取 A.z(值为 10)。
- 即使 B 中有同名字段,它对 A 类型的引用完全不可见(除非强制转型:((B) obj).z 才能访问 20)。
A obj = new B(); System.out.println(obj.z); // 编译期绑定 → A.z = 10 System.out.println(((B) obj).z); // 强制转型后访问 B.z = 20
⚠️ 注意:若 B 中将 z 声明为 String z = "hello";,则 obj.z 仍合法(因 A 中 z 是 int),但 ((B) obj).z 将返回字符串——进一步印证二者是完全独立的字段,无任何覆盖关系。
方法:动态绑定(真正重写)
与字段不同,非静态、非私有、非 final 的实例方法支持运行时多态。当 obj.display() 被调用:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
立即学习“Java免费学习笔记(深入)”;
- 编译期检查 A 类是否有 display() 方法(有,故通过编译);
- 运行期根据实际对象类型(new B())查虚方法表(vtable),定位到 B.display();
- B.display() 内部访问的是 this.z → 此时 this 是 B 实例,this.z 指向 B 类定义的字段(20)。
因此输出 "B's z is 20" ——这不是“A 的方法用了 B 的字段”,而是B 自己的方法访问了 B 自己的字段。
关键对比总结
| 特性 | 字段(Field) | 实例方法(Instance Method) |
|---|---|---|
| 绑定时机 | 编译期(静态绑定) | 运行期(动态绑定) |
| 多态支持 | ❌ 不支持(仅隐藏) | ✅ 支持(可重写) |
| 访问依据 | 引用类型(A obj → A.z) | 实际对象类型(new B() → B.display()) |
| 设计意图 | 表示状态,应封装(推荐 private) | 表示行为,需扩展(鼓励重写) |
✅ 最佳实践建议:
- 始终将字段声明为 private,通过 getter/setter 控制访问;
- 若需子类定制状态逻辑,应在父类中提供 protected 或 public 的 getter 方法,并在子类中重写该方法(而非隐藏字段):
class A {
private int z = 10;
public int getZ() { return z; } // 可被重写
public void display() { System.out.println("A's z is " + getZ()); }
}
class B extends A {
private int z = 20;
@Override public int getZ() { return z; } // 真正的多态行为
}这样,A obj = new B(); obj.display() 将自然输出 "A's z is 20",语义清晰且符合面向对象设计原则。

















