字段访问看声明类型,方法调用看实际类型:字段隐藏是编译期静态绑定,子类同名字段独立存在且不覆盖父类字段;方法覆盖是运行期动态绑定,jvm从实际对象类向上查找最具体的重写版本。

属性隐藏:编译期看声明类型,运行期不动态绑定
Java 中的属性(字段)不会被“覆盖”,只会被“隐藏”。当子类定义了与父类同名的字段时,子类字段会隐藏父类字段,但二者在内存中独立存在。查找哪个字段被访问,取决于变量的声明类型,而非实际对象类型。
例如:
class A { int x = 10; }
class B extends A { int x = 20; }
class C extends B { int x = 30; }
A a = new C();
System.out.println(a.x); // 输出 10 —— 看 a 的声明类型 A
即使 a 实际指向 C 实例,a.x 仍访问的是 A 类中定义的 x。若想访问子类字段,需强制转型:((C)a).x → 30。
方法覆盖:运行期看实际类型,遵循最具体重写版本
实例方法(非 static、非 private、非 final)支持动态绑定。JVM 在运行时从实际对象的类开始,沿继承链向上查找第一个可访问的、签名匹配的重写方法(即“最具体的实现”)。
查找规则:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 从对象的实际运行时类(如
C)出发,检查是否定义了匹配的方法 - 若未定义,继续查直接父类(
B),再往上到A - 一旦找到,立即使用该版本(不继续向上找更“原始”的版本)
- 静态方法不参与覆盖,只按声明类型解析(属于隐藏)
示例:
class A { void m() { System.out.println("A.m"); } }
class B extends A { @Override void m() { System.out.println("B.m"); } }
class C extends B { @Override void m() { System.out.println("C.m"); } }
A a = new C();
a.m(); // 输出 "C.m" —— 调用 C 类中重写的版本
多层继承中字段与方法混用的常见陷阱
当子类既有同名字段又有重写方法时,容易混淆行为来源。关键要分清:字段访问是静态解析,方法调用是动态分派。
典型误区:
-
this.x在C的方法里访问的是C.x(因为this类型就是C),但通过父类引用访问仍是父类字段 - 构造器中调用被重写的方法,可能访问到子类尚未初始化的字段(因子类构造器还没执行),导致意外的 0 或 null
- 在
A的方法里写System.out.println(x),输出的是A.x;若该方法被C重写,在C.m()中写同样语句,输出的是C.x
如何验证查找过程:用 javap 辅助观察
编译后使用 javap -c 查看字节码,能清晰区分字段访问指令(getfield)和方法调用指令(invokevirtual):
-
getfield后紧跟的是字段所属的类名(编译期确定) -
invokevirtual后是方法声明的类名,但实际调用目标由运行时对象决定 - 对
static方法,字节码用invokestatic,完全不涉及继承链查找
这是理解 JVM 底层行为的可靠方式,比仅靠 IDE 提示更准确。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










