Java动态绑定问题:调用computeDiscount方法为何最终输出结果为5?
这个问题本质是混淆了Java中方法重载的静态绑定和方法重写的动态绑定的执行逻辑,我们一步步拆解就能得到结果:
// 代码核心结构 class Customer { } class RegisteredCustomer extends Customer{} class Shop { public int computeDiscount(Customer c){return 0;} } class OnlineShop extends Shop { // 重写父类方法,签名一致:computeDiscount(Customer) public int computeDiscount(Customer c){return 5;} // 重载方法,新签名:computeDiscount(RegisteredCustomer) public int computeDiscount(RegisteredCustomer c){return 15;} } class OnlinePremiumShop extends OnlineShop{ // 重载父类方法,签名:computeDiscount(RegisteredCustomer) public int computeDiscount(RegisteredCustomer c){return 20+super.computeDiscount(c);} } // main方法 public static void main(String[] args) { RegisteredCustomer c3 = new RegisteredCustomer(); Shop s2 = new OnlinePremiumShop(); System.out.println(s2.computeDiscount(c3)); // 输出5 }
核心概念区分
先理清两个绑定规则,这是问题的核心:
- 方法重载(Overload):同一个类中方法名相同、参数列表不同的方法,属于编译期静态绑定,只根据调用对象的*静态类型(声明时的类型)*和参数的静态类型确定要调用的方法签名,和运行期的实际对象类型无关
- 方法重写(Override):子类定义和父类方法名、参数列表、返回值完全一致的方法,属于运行期动态绑定,根据调用对象的*动态类型(实际指向的对象类型)*决定最终执行哪个类的实现
执行过程拆解
第一步:编译期确定方法签名
调用方法的对象是s2,它的静态类型是Shop,编译器只会在Shop类的定义中查找匹配的computeDiscount方法:
Shop类中只有1个computeDiscount方法,签名为computeDiscount(Customer c),即使传入的参数c3静态类型是RegisteredCustomer(Customer的子类,能向上转型匹配),编译期最终确定的调用签名就是computeDiscount(Customer c)。
注意:子类OnlineShop、OnlinePremiumShop中定义的
computeDiscount(RegisteredCustomer c)是全新的重载签名,父类Shop中没有这个签名的方法,编译期完全不会考虑这些重载方法。
第二步:运行期匹配对应签名的重写实现
运行期s2的动态类型是OnlinePremiumShop,我们只需要查找computeDiscount(Customer c)这个签名的重写实现即可:
- OnlinePremiumShop没有重写
computeDiscount(Customer c)这个签名的方法,只定义了参数为RegisteredCustomer的重载方法,不符合匹配要求 - 向上查找父类OnlineShop,OnlineShop重写了
computeDiscount(Customer c)方法,返回值为5 - 最终执行这个实现,输出结果为5
以后遇到类似多态方法调用的问题,严格按以下4步分析即可:
- 找到调用方法的对象,先看它的静态类型(等于变量声明时左边的类型),在这个类型的定义中,找出所有和调用方法名相同、参数个数匹配的方法,选择适配参数静态类型的最匹配签名,这就是编译期确定的最终调用签名,子类的重载方法这一步直接忽略
- 再确定这个调用对象的动态类型(等于变量赋值时右边new的类型)
- 从这个动态类型开始,依次向上查找父类,找有没有对第一步得到的完全相同签名的方法的重写
- 找到继承链中最下层的重写实现,执行结果就是最终输出
扩展验证:如果把s2的声明改成
OnlineShop s2 = new OnlinePremiumShop();,编译期就能在OnlineShop中匹配到computeDiscount(RegisteredCustomer c)的签名,运行期就会调用OnlinePremiumShop的对应实现,输出20+15=35。
内容的提问来源于stack exchange,提问作者ctrlaltdel

