
当子类对象被赋值给父类引用(如 A a = new B();)时,方法调用遵循动态绑定(运行时多态),而字段访问则基于引用的编译时类型——即只访问父类中定义的字段,子类同名字段会被隐藏而非覆盖。
当子类对象被赋值给父类引用(如 `a a = new b();`)时,方法调用遵循动态绑定(运行时多态),而字段访问则基于引用的**编译时类型**——即只访问父类中定义的字段,子类同名字段会被隐藏而非覆盖。
在 Java 中,向上转型(upcasting)是安全且隐式的:A a = new B(); 合法成立,因为 B 是 A 的子类。但这一操作对字段(field) 和方法(method) 的影响截然不同,根源在于 Java 的绑定机制差异:
✅ 方法调用:动态绑定(Late Binding)
调用 a.show() 时,JVM 在运行时检查 a 实际指向的对象类型(B),发现 B 重写了 show(),因此执行 B.show(),输出 "B show"。这是多态的核心体现。-
❌ 字段访问:静态绑定(Early Binding)
表达式 a.getX() 返回 10,而非 20,原因在于:- getX() 是 A 类中定义的方法,其内部直接返回 this.x;
- this 在 getX() 执行时指向 B 实例,但 this.x 的解析依据是方法所在类的声明(即 A 类的 x 字段);
- 更关键的是:B 类中声明了 int x = 20;,这并非覆盖(override)父类字段,而是隐藏(hiding) —— B 实例内存中实际包含两个独立的 x 字段:一个来自 A(值为 10),一个来自 B(值为 20)。
- 由于 getX() 在 A 中定义,它只能访问 A 自身的 x,因此返回 10。
可通过以下代码验证字段共存现象:
class B extends A {
int x = 20;
public void printBothX() {
System.out.println("A.x (via super): " + super.x); // 10
System.out.println("B.x (own field): " + this.x); // 20
}
}⚠️ 注意事项:
- 字段不能被重写(override),只能被隐藏(hiding)。这是 Java 语言规范明确规定的语义,与方法完全不同;
- 同名字段隐藏极易引发歧义和 Bug,强烈建议避免在子类中声明与父类同名的实例字段;
- 若需扩展或修改字段行为,应通过受保护的 getter/setter 方法实现,并在子类中重写这些方法(如 getX()),而非直接定义同名字段;
- 编译器不会警告字段隐藏,但 IDE(如 IntelliJ)通常会提示 “Field ‘x’ hides a variable in superclass”。
✅ 正确实践示例:
立即学习“Java免费学习笔记(深入)”;
class A {
protected int x = 10;
public int getX() { return x; }
}
class B extends A {
public B() {
this.x = 20; // 修改继承来的同一字段,而非新建一个
}
// 不重定义 int x!
}
// 此时 A a = new B(); a.getX() 将返回 20总结:理解“方法看运行时类型,字段看编译时类型(及定义位置)”是掌握 Java 多态的关键。向上转型后,对象的“身份”(方法行为)由实际类型决定,而其“结构视图”(字段可见性)则由引用类型锁定。


















