首先我们必须弄清楚什么是多态
首先多态是在封装的基础上才有的(因为对象由封装而成)
其次多态指的是:同类型的对象,表现得不同的形态。
它的表现形式为:Father f=new Son();
多态还有三个前提:
1.有继承关系
2.有父类引用指向子类对象:即子类对象赋值给子类类型
比如Person是Student 和Teacher的父类
我们创建了一个方法 public void register(Person p) 这里的形参可以用Student或者Teacher类型的象传入
3.有方法重写(子类中成员方法对父类的方法的重写,即覆盖虚方法表中的方法)
接下来我们看一下多态调用成员的例子
首先创建父类(Animal)和子类(Dog),具体如下图:
![](https://img-blog.csdnimg.cn/img_convert/192c7ddea02adbf238e33eaa0752b842.png)
![](https://img-blog.csdnimg.cn/img_convert/37694005b2c8f10e3ded6329f3a53299.png)
接下来看main方法中如何调用:
![](https://img-blog.csdnimg.cn/img_convert/91d85fb436b6e88d0aeb467bf7372c9e.png)
我们看到了父类类型(Animal)的对象调用了子类(Dog)的构造方法(new Dog()),这一个经典的多态形式
接下来我们直接输出a.name,得到的结果是"动物",是我创建在父类中的变量初始值,而不是子类中的"狗"
接下来我们从内存角度分析下整个运行过程:
![](https://img-blog.csdnimg.cn/img_convert/257c01f2a1c5e456f03f2594d5825b7f.png)
①如图,首先加载main方法的字节码文件
②看到main()中第一行出现Animal类,继续加载Animal.class这一字节码文件,同时加载Animal类中的成员变量和成员方法,加上要传给子类的虚方法表(其中就有show()这一方法)
③又看到new Dog(),继续加载Dog.class这一字节码文件,然后加载其中的成员变量和成员方法,加载完方法后发现,我们的Dog类中的show()方法对父类的show()做了重写操作,更新自己的虚方法表,使其中的show()方法是自己重写的内容
④因为是用Dog()这一构造方法new了一个Animal类型的对象 a(内存中地址是001),所以要在堆内存中创建 Animal对象,其中存有两个成员变量的值(父类和子类的name值),左边是父类的name="动物",右边是子类中的name="狗"。
⑤我们看到了sout(a.name)让我们打印a.name的值,因为a是Animal类型的对象,
编译的时候是先判断a所属的Animal类中有没有叫name的成员变量,有则编译通过,没有则编译失败;
运行的时候是直接看左边父类Animal(因为a就是Animal类型)中的name的值,直接打印父类的name值:"动物"
(变量调用:编译看左边,运行也看左边)
补充:如果是Dog d=new Dog()创建的话,d.name就是先看右边子类中有没有,没有再看左边父类中有没有name值
⑥这时候我们看到代码进行到了a.show()。
从编译的角度,JVM会去找a所在Animal类中有没有叫show()的方法,有则编译通过,没有则编译失败。
运行时:实际上是相当于查看最新的虚方法表,刚刚第三步加载Dog.class的时候,我们就已经通过Dog类中重写的show()更新了虚方法表中的show(),所以我们运行时调用的就是Dog类中的show()
(方法调用:编译看左边,运行看右边)
最后的运行结果:
![](https://img-blog.csdnimg.cn/img_convert/a4f71b3f02847c4ab33ae47558bb746d.png)
总结:
在使用面向对象中多态调用的时候,要区分调用成员变量和成员方法,
他们的编译判定都是看对象类中存不存在这样的变量和方法,有则编译,无则报错
运行的时候:输出的变量是输出对象数据类型那个类当中的成员变量
输出的方法是看(重写后)最新的虚方法表中的方法