Java动态绑定疑问:为何运行时未调用更具体的子类方法?
Java动态绑定与方法调用的核心问题解析
问题场景
类继承关系:WTC <: Skyscraper <: Building(WTC继承自Skyscraper,Skyscraper继承自Building)
类定义:
class A { public void foo(Building f) { } } class B extends A { public void foo(_BLANK_ f) { } }
调用代码(注:原代码存在笔误,实际应为A building = new B();,否则Building类无foo方法无法编译,修正后不影响核心问题):
Skyscraper skyscraper = new Skyscraper(); A building = new B(); building.foo(skyscraper);
问题与选项
在WTC、Skyscraper、Building、Object四种类型中,_BLANK_填哪种类型时,上述foo调用会执行A::foo?
选项:
- A. 仅WTC
- B. 仅WTC或Skyscraper
- C. 仅WTC或Object
- D. 仅Skyscraper或Building
- E. 仅Skyscraper、Building或Object
- F. 以上选项均不正确
给定答案为F,存在以下困惑:
- 已知填Building时会调用
B::foo,但为何填Skyscraper时也会被认为可调用B::foo? - 为何JVM运行时不会识别
B::foo(Skyscraper)比A::foo(Building)更具体,转而调用前者?
核心原理拆解
Java的方法调用分为编译期静态绑定和运行期动态绑定两个完全独立的阶段,这是解开困惑的关键:
1. 编译期:确定方法签名(静态绑定)
编译时,编译器仅根据变量的引用类型(这里是A类型的building)和参数的编译期类型(这里是Skyscraper)来选择最匹配的方法:
- 编译器会遍历
A类及其父类的方法列表,找到与调用参数兼容的方法。A类只有foo(Building),而Skyscraper是Building的子类,可以向上转型为Building,因此编译期确定的方法签名是foo(Building)。 - 重载方法的选择完全在编译期完成,运行时不会重新评估参数的实际类型来切换重载方法。
2. 运行期:动态绑定实现类的方法
运行时,JVM只会根据编译期确定的方法签名,在实例的实际类型(这里是B)中查找对应方法:
- 如果
B类存在该签名的方法(即_BLANK_填Building时,B::foo(Building)是对A::foo(Building)的重写),则调用B的方法。 - 如果
B类没有该签名的方法(即_BLANK_填Skyscraper、WTC、Object时,B的foo方法是重载,签名与foo(Building)不同),则向上查找父类A的对应签名方法,即执行A::foo。
逐个分析填空情况
- 填Building:
B::foo(Building)是重写A的方法,运行时调用B::foo。 - 填Skyscraper:
B::foo(Skyscraper)是重载方法,编译期确定的签名是foo(Building),B无此签名方法,执行A::foo。 - 填WTC:同Skyscraper,属于重载,执行
A::foo。 - 填Object:
B::foo(Object)是重载方法,编译期A::foo(Building)是更匹配的选择,B无foo(Building),执行A::foo。
可见,填Skyscraper、WTC、Object时都会执行A::foo,填Building时执行B::foo,现有选项A-E均不匹配,因此答案为F。
对困惑的解答
- 填Skyscraper时,
B::foo(Skyscraper)是独立的重载方法,和A::foo(Building)没有重写关系,编译期根本不会选择这个方法签名,因此运行时JVM也不会去调用它。 - JVM运行时不会重新选择重载方法,因为方法签名在编译期就已固定,动态绑定只针对同一签名的重写方法,不会根据参数实际类型切换重载方法。
内容的提问来源于stack exchange,提问作者math_learner
相关产品推荐
相关产品推荐

