由实际接收对象决定

若父类型变量保存的是子类型对象,调用被重写的实例方法时,可以执行子类型提供的实现。Java 教程用 Animal 类型引用保存 Cat 对象,展示实例方法调用如何到达 Cat 的版本。

这和同名静态方法的隐藏不同。静态方法选择不按上述对象重写规则工作,所以只观察方法名字或变量声明,无法把所有调用一概视为同一种派发。

Java中还须把重载选择与动态查找分开。若同时存在visit(Object)和visit(String),一个静态类型为Object的参数即使此刻保存String,普通调用也不会因此在运行时自动改选String重载;编译期先确定方法签名,再按适用规则为该签名查找实际接收对象的实现。这是Java所规定的分工,不能推广成所有语言都只能按一个对象派发。

参考:[1] [4]

接口指针与方法表

Rust 的 trait object 让不同具体类型通过共同 trait 接口使用。其指针携带数据指针以及虚方法表信息,表中的函数指针对应具体实现,调用时据此选择方法。

因此,一个集合可以保存不同具体类型的对象,只要它们满足所需接口。接口限制了调用方能够要求的操作,具体数据表示则仍由各个实现保有。

参考:[2] [3]

灵活性、静态分派与边界

Rust 的泛型也可以通过单态化生成针对具体类型的代码,此时目标在编译期已知,是另一条实现多态的路径。动态分派提供运行时灵活性,却引入间接选择,并可能限制内联等优化。

这些是实现上的取舍,不能据此断言所有动态调用都慢,或所有泛型都采用相同策略。满足调用接口也只保证可调用,仍须另外检查实现行为是否符合约定。

参考:[2] [3]