多态
多态从语法表面来看,就是子类对象可以赋值给父类引用,并通过引用,调用不同子类的方法。
多态的实际用法分为:
- 继承多态
- 接口多态
class Son extends Father {
@Overrid
public void smoke() {
System.out.print("儿子抽烟");
}
}
class Daughter extends Father {
@Overrid
public void smoke() {
System.out.print("女儿抽烟");
}
}
// 继承多态,因为Son、Daughter继承了Father
Father obj = new Son();
obj.smoke(); // 打印:儿子抽烟
obj = new Daughter();
obj.smoke(); // 打印:女儿抽烟
class Son implements Swimmer {
@Overrid
public void swim() {
System.out.print("儿子游泳");
}
}
class Daughter implements Swimmer {
@Overrid
public void swim() {
System.out.print("儿子游泳");
}
}
// 接口多态,因为Son、Daughter实现了Swimmer
Swimmer obj = new Son();
obj.swim(); // 打印:儿子游泳
obj = new Daughter();
obj.swim(); // 打印:女儿游泳
实际开发接口多态更常用。多态的实现,依赖于2个大方面:
● 机制上的支持
● 编码上的支持
机制上的支持:
首先,编译器要允许这种赋值方式,不然把son赋值给swimmer就会像把 int a赋值给String b一样报错。
其次,运行时要支持并且能通过某种机制找到真正的子类方法。
编码上的支持:
必须存在继承(实现)关系 + 子类必须重写(实现)父类的方法
那么问题又来了,JVM怎么知道要打印儿子吸烟呢?话句话说就是为什么JVM知道要调用son#smoke(),而不是daughter#smoke()呢?
这就涉及到了虚方法和需方发表了,JVM有个类加载子系统,专门负责类的加载:
而在类加载的过程中,有loading、linking和initialization三个阶段,其中linking阶段又包括3个小阶段:
- 验证
- 准备
- 解析
在解析阶段,JVM会针对类和接口、字段、类方法、接口方法等进行解析,方法信息会形成虚方法表,
也就是说,当出现多态方法调用时,底层会多一次“查表”的过程,也就是通过搜索虚方法表,确定本次实际应该调用的方法(实际指向对象+实例对应的类有无重写父类方法),如果子类Override了父类方法,那么就会执行子类方法。