bigywater.tech
Java 内部原理 · 133

静态字段隐藏真实验证:字段访问真实按引用的声明类型决定,和方法多态完全不同

Child继承Parent并声明同名字段label,很多人以为这也是"覆盖"——这篇真实用Parent p = new Child()构造出引用类型和运行时类型不一致的场景,对比字段访问和方法调用的真实差异。

字段真实按声明类型决定
真实验证:p.label和p.instanceLabel(引用类型是Parent)真实返回Parent-static和Parent-instance,即使运行时对象是Child
方法真实按运行时类型决定
真实验证:同样是p(引用类型Parent,运行时对象Child),p.describe()真实返回Child-method——方法调用是真正的多态

上一篇:多维数组内存真实验证

真实验证:静态字段和实例字段都真实不支持多态

Parent和Child各自声明同名的static字段label和实例字段instanceLabel,用Parent p = new Child()这种"父类引用指向子类对象"的经典场景来测试。

StaticFieldHidingDemo.java static class Parent { static String label = "Parent-static"; String instanceLabel = "Parent-instance"; } static class Child extends Parent { static String label = "Child-static"; String instanceLabel = "Child-instance"; } Parent p = new Child();
真实实测
真实p.label(引用类型是Parent) 结果: Parent-static
真实Child.label 结果: Child-static
真实Parent.label 结果: Parent-static
真实p.instanceLabel(引用类型是Parent) 结果: Parent-instance
真实c.instanceLabel(引用类型是Child) 结果: Child-instance
真实((Child)p).instanceLabel 结果: Child-instance

真实结果证明:不管是static字段还是普通实例字段,p.label和p.instanceLabel真实都返回了Parent版本的值,尽管p运行时指向的真实是一个Child对象。这是因为字段访问在编译期就真实按变量的声明类型(Parent p)静态绑定了要访问哪个字段,和这个引用运行时实际指向什么对象完全无关。真实测试还验证了强制转型((Child)p).instanceLabel后立刻切换回Child版本——因为转型改变了编译器用来做字段绑定的静态类型。

真实对照:方法调用同样条件下真实按运行时类型决定

StaticFieldHidingDemo.java static class Child extends Parent { @Override String describe() { return "Child-method"; } // 真实覆盖 } p.describe(); // p的引用类型仍是Parent
真实实测
真实p.describe()(引用类型Parent,实际对象Child) 结果: Child-method
同一个变量p,同一行代码结构,字段和方法真实给出了相反的答案。这不是巧合,而是Java语言规范里两条完全不同的规则:字段访问在编译期真实按声明类型静态解析(和C++里没有virtual关键字的成员变量行为一致);而实例方法调用真实通过对象头里的方法表做动态分派(虚方法调用),运行时看的是对象的真实类型,不是引用的声明类型。真实结果里p.label和p.describe()用几乎相同的调用形式,却分别体现了这两条互相矛盾的规则——这正是"字段隐藏(field hiding)"和"方法覆盖(method overriding)"这两个术语被特意分开命名的原因:它们看起来像同一件事,底层机制却完全不同。真实的编码建议是:永远不要在父子类里定义同名字段,即便本意不是想利用这个隐藏特性,它也极容易在"用父类引用操作子类对象"的场景下造成真实的逻辑错误。

分步演示:强转是怎么"切换"字段绑定的

每一行都是真实运行这段代码得到的结果。

Parent p = new Child():静态字段、实例字段都按 p 的声明类型走,方法调用却按 Child 的运行时类型走
这一步新验证的访问

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,字段隐藏和方法覆盖的行为差异重复运行完全一致。
    • 和字段隐藏真实验证一起看(那篇聚焦子类内部this.field的隐藏细节),能更完整理解Java里"字段绑定看声明类型、方法绑定看运行时类型"这条贯穿整个语言的核心规则。
    • 没有涉及:接口中的字段(隐式static final)在多重继承场景下的真实名字冲突规则、字节码层面getfield/putfield指令如何体现这种编译期静态绑定——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号