方法调用分为解析调用和分派调用两大类。
解析调用
解析调用是一个静态过程,在编译期间进行。
静态解析的方法需要满足条件“编译器可知,运行时不可变”,此类方法也被称为非虚方法,包括静态方法(直接与类型关联),私有方法(外部不可访问),实例构造器和父类方法。final方法也是一种非虚方法。
分派调用
分派调用有静态分派和动态分派(好像动态绑定这个名字用的更多)。
静态分派:根据静态类型定位方法执行版本的分派行为,静态分派发生在编译期间。典型应用:方法重载。
动态分派:根据实际类型定位方法执行版本的分派行为,动态分派发生在运行期间。典型应用:方法重写。
示例(Kotlin代码)
运行结果:
I am a man.
I am a woman.
Human.
Human.
其中,a,b的静态类型都是Human。可以看出,方法重载依据静态类型进行分派,方法重写依据动态类型进行分派。
分派还分为单分派和多分派,主要是看分派行为所依据的条件个数,比如动态分派,只依据实际类型进行分派,就是单分派。但是感觉这个东西用处不大。。
动态绑定的实现
C++的多态主要是通过虚表和虚指针实现的。Java也类似,主要是通过方法表来实现的。
父类和子类的方法表偏移量是一致的,这是一个关键。
在动态绑定的过程中,首先会得到父类的方法表中该方法的偏移量,然后根据this指针得到该对象的动态类型(实际类型),根据偏移量找到该类型方法表中对应的方法,如果有重写父类的该方法,则直接调用,如果没有,则按照继承关系向上查找。
而在接口继承中,由于类可以继承多接口,因此偏移量可能会不同,这时,每次方法调用都要搜索方法表。因此,接口调用的效率比类调用要低一些。