多态的机制及分类
一个对象变量可以指示多种实际类型的现象称为多态;允许不同类的对象对同一消息做出响应。方法的重载、类的覆盖(继承和实现)正体现了多态;
重载(overload 发生在一个类中,方法名必须相同,不同参数)就是编译时多态的一个例子,编译时多态在编译时就已经确定,运行时运行的时候调用的是确定的方法;
我们通常所说的多态指的都是运行时多态,也就是编译时不确定究竟调用哪个具体方法,一直延迟到运行时才能确定,这也是为什么有时候多态方法又被称为延迟方法的原因;
多态的实现原理
动态绑定和静态绑定
JVM在执行方法时,通常调用的指令有五个,分别是:
invokestatic:调用静态方法;
invokespecial:调用实例构造器方法、私有方法和父类方法;
invokevirtual:调用虚方法;
invokeinterface:调用接口方法,运行时确定具体实现;
invokedynamic:运行时动态解析所引用的方法,然后再执行,用于支持动态类型语言。
其中Invokestatic和invokespecial指令是用于静态绑定,invokevirtual 和 invokeinterface 用于动态绑定。
静态绑定指的是在编译期就能确定的,比如静态方法、构造器、私有方法和父类方法这4种方法在编译期就能解析引用确定的称为非虚方法,与之相对的就是虚方法,对象方法基本都为虚方法,例如某个类的drive(Car car),汽车子类实现类中的run()方法等;有个特例的是被final修饰的方法,由于不能被继承重写,所以是可以唯一确定的,是属于非虚方法,但却是使用invokevirtual指令调用的;
JVM底层多态实现过程
多态的实现过程,本质就是方法调用动态绑定的过程,通过栈帧的信息去找到被调用方法的具体实现,然后使用这个具体实现的直接引用完成方法调用;
invokevirtual指令在运行时解析大致分为以下几个步骤:
- 先从操作栈中找到对象的实际类型C;
- 找到C中与被调用方法签名相同的方法,如果有访问权限就返回这个方法的直接引用,如果没有访问权限就报错java.lang.IllegalAccessError ;
- 如果第2步找不到相符的方法,就去搜索C的父类,按照继承关系自下而上依次执行第2步的操作;
- 如果第3步找不到相符的方法,就报错java.lang.AbstractMethodError ;
可以看到,如果子类覆盖了父类的方法,则在多态调用中,动态绑定过程会首先确定实际类型是子类,从而先搜索到子类中的方法;
在程序运行时,动态绑定是一个非常高频的行为,JVM为了提高性能,避免反复搜索对应类型的绑定数据,会建立一个对应的虚方法表和接口方法表,使用对应的表索引来提高查询的性能;
以虚方法表为例,如果某个父类的方法在子类中没有被进行重写,那么此时子类和父类的虚方法表指向的地址是一致的,只有当出现子类重写了父类的方法时,才会出现子类的虚方法表与父类的虚方法表指向的地址不一样。因此如果子类没有重写父类的方法时,会随着父类的方法解析一同解析;