本文参考《深入理解Java虚拟机第三版》8.3章节
首先我们来看下面这段代码:
public class Test1 {
public static void main(String[] args) {
People p1=new Man();
People p2=new Woman();
p1.say();
p2.say();
}
}
class People{
void say(){
System.out.println("你好,爷爷");
}
}
class Man extends People{
void say(){
System.out.println("你好,爸爸");
}
}
class Woman extends People{
void say() {
System.out.println("你好,女儿");
}
}
输出结果:
对于这个结果大家都不会意外,子类对象赋给父类引用,通过父类引用调用的方法是子类中重写的方法。
那么问题来了,为什么通过父类引用调用方法时会调用子类中定义的方法呢?
很多人是这样理解的:当我们在子类中重写方法时,会将父类中定义的方法覆盖掉,也就是说当执行People p1=new Man();
时,只会将子类中定义的方法加载到方法区中。
但是,事实并非如此,创建对象时,父类与子类中定义的方法会依次加载到方法区中。
那么父类的引用是如何定位到子类中重写的方法的呢?
在解答这个问题之前需要先了解一下方法调用指令。实际上,对于不同类型的方法,字节码指令集中设计了不同的调用指令,不同的指令有不同的实现逻辑。Java中默认的方法都是虚方法,也就是可以重写的方法(一般只会Java的人没有虚方法的概念,该概念由C++衍生而来,实际上在Java中我们可以重写的方法都是虚方法),同时还存在其它类型的方法,例如:static方法、final方法。虚方法的调用指令是invokevirtual。
invokevirtual指令的运行时解析过程大致如下:
1,找到引用所指向的对象的实际类型。 //对于People p1=new Man();
,其静态类型为People,实际类型为Man。
2,如果在实际类型中找到名称相符的方法,则返回该方法的直接引用,接着调用该方法。 //对于p1.say();
,实际调用的是Man中定义的方法
3,如果没找到,按照继承关系从下往上在其各个父类中进行第2步
至此,终于明白,当我们使用父类引用调用方法时,会从引用指向的对象的实际类型中去找该方法。如果引用指向的是不同实际类型的对象,则调用的方法也会不同。多态也因此而实现。
需要注意的是,该多态是运行时多态,对于People p1=new Man();
,其静态类型在编译器就已确定,实际类型在编译器无法确定,运行时才确定。
例如:People p3=(new Random()).nextBoolean() ? new Man() : new Woman();
,p3的静态类型为People,编译器就可以确定,但其实际类型无法确定,只有程序执行到这行代码时才能确定。
注:本人也在学习阶段,如有误请指正!