目录
什么是多态?
从字面上理解,多态其实就是事物的多种状态,类似于学生可以有人的状态,同时可以作为儿女状态;一个正方形,同时可以是矩形的状态,同时也可以是多边形的状态;
多态实际代码应用场景
为什么要有多态?想学习多态,先从它能解决什么问题入手
下面我来引入两个例子
例子1
- 当我们开发学生管理系统的注册功能时,由于这个系统服务于学生、老师和管理人员,注册账号时我们通过一个方法来表示注册,这时要传什么参数就懵了
public void register(Student s){
//注册的代码
}
- 注册功能应该允许学生、老师、管理员进行注册,而如上代码我们只能传递三中参数之一,那么你可能会想,那我写三个方法不就行了,如下代码
public void register(Student s){
//注册的代码
}
public void register(Teacher t){
//注册的代码
}
public void register(Manager m){
//注册的代码
}
- 但你会发现,我们注册的代码写了三份,这样做复用性极低,也增加了维护难度
- 比较正确的做法应该是给这三个类抽象出一个父类Person(人),使用它指向它的子类
public void register(Person p){
//注册的代码
}
- 这样做它所有的子类都可以作为参数传递进来,那么注册的代码就只需要写一份。体现了多态特点,父类做类型,子类做实现,做到了可以灵活更换子类实现
例子2
- 有这样一个需求,让你写一个动物Animal类,然后有Dog(狗)、cat(猫)、panda(熊猫)三个子类继承于Animal类,Animal类中提供了"吃"的方法。
public class Animal {
public void eat(){
System.out.println("动物正在吃食物");
}
}
//Dog
public class Dog extends Animal{
}
//Cat
public class Cat extends Animal{
}
//Panda
public class Panda extends Animal{
}
- 但是三种子类吃的东西可能不一样,那么我们使用多态实例化子类对象(就是父类 变量名 = new 子类;例如Animal a = new Dog();)时,调用吃的方法出现的是父类Animal中提供的"吃"的方法,我们要实现每个对象调用"吃"的方法都能输出对应状态的"吃"的方法,就可以在子类中重写"吃"这个方法
//Animal
public class Animal {
public void eat(){
System.out.println("动物正在吃食物");
}
}
//Dog
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗正在啃骨头!");
}
}
//Cat
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫正在舔着小鱼!");
}
}
//Panda
public class Panda extends Animal{
@Override
public void eat() {
System.out.println("熊猫正在吃竹子!");
}
}
- 我们在Demo中进行测试
public class ObjectDemo {
public static void main(String[] args) {
//多态方式实例化对象
Animal dog = new Dog();
Animal cat = new Cat();
Animal panda = new Panda();
//调用eat方法
dog.eat();
cat.eat();
panda.eat();
}
}
-
结果
-
你可能会想,那我为什么不直接Dog a = new Dog() / Cat cat = new Cat() / Panda panda = new Panda()呢?因为就是为了体现多态的好处,例如下面代码,我们在同例子1 中的调用方法传参后就跟上面使用多态方式实例化没区别了,就是类型都是父类引用
public class ObjectDemo {
public static void main(String[] args) {
//多态方式实例化对象
Dog dog = new Dog();
Cat cat = new Cat();
Panda panda = new Panda();
//调用eat方法
eatMethod(dog);
eatMethod(cat);
eatMethod(panda);
}
public static void eatMethod(Animal a){
a.eat();
}
}
- 运行结果也是一样的
- 多态的思想也引出了面向父类编程思想,用父类做类型,子类做实现
多态详解
多态是一种面向对象编程的思想,有多种实现方式,如我们所知的方法重载、方法重写等
编译时多态
编译时多态指在编译时决定目标方法,就是在编译时就知道使用哪一个方法
通过overloading重载实现,可以说重载就是编译时多态
特点就是方法名相同,参数不同
运行时多态
运行时多态指在运行时决定目标方法,就是只有在运行时才知道使用的是哪个方法
通过override重写实现,可以说重写就是运行时多态
特点就是方法名相同,参数相同
这也可以看出,继承是运行时多态的前提
两种多态成员调用时的特点
- 开发中只需要牢记以下口诀即可
- 成员变量编译时看左边,运行时也看左边
- 成员方法编译时看左边,运行时看右边
下面对口诀进行详解
编译时多态成员调用特点
编译时无论是成员变量还是成员方法,都是看左边
这个看左边指的就是看多态等号左边的类型,多态的情况下就是父类
-
多态调用成员变量,编译时会先在父类中找,不管子类,只要父类中有那么就编译成功,没有就编译失败报错
父类中有目标成员变量的情况,编译没有报错,且IDEA提示我们使用的是父类Animal中的name变量
父类中没有目标成员变量而子类有的情况,它在父类中找不到目标成员变量,就报错,而不管子类有没有
- 多态调用成员方法,编译时看左边,如果左边是父类,那么同样父类中有目标方法则编程成功,父类中没有目标方法则编译失败,不管子类
父类中有目标成员方法的情况,编译没有报错
父类中没有目标成员方法而子类有的情况,它在父类中找不到目标成员方法,就报错,而不管子类有没有
运行时多态成员调用特点
运行时成员变量看左边(等号左边),成员方法看右边(等号右边)
- 多态调用成员变量,在运行时获取的是左边类中的值,这里便不做演示,类似于编译时多态调用成员变量,只看父类中的成员变量不管子类
- 多态调用成员方法,在运行时执行的是右边子类中的方法,也就是运行重写的方法
Java运行时多态的实现原理
其实我们观察多态成员调用特点不难发现,只有运行时多态的成员方法调用比较特别
- Java是如何去确定我该调用哪个方法呢?
- 那便是通过虚方法表,JVM虚拟机中有一个方法区,其中存储了class字节码文件并在下面挂了虚方法表,一个class字节码文件对应一个类,虚方法表也具有继承性
- 对应的子类中如果重写了方法,那么子类的虚方法表就会进行覆盖,前面说过运行看右边(子类),所以运行的方法就是子类虚方法表中记录的方法,也就是重写的方法
多态的优势和弊端
多态的优势
优势在前面已经有所介绍
- 在多态的形式下,实例化时右边子类对象可以实现解耦合,我们想修改功能直接修改子类中的代码即可,并不需要修改到主类中的代码逻辑
- 定义方法的时候参数采用多态的形式,可以灵活接收所有子类对象,当有新增的子类时我们不必修改原有代码逻辑,只需完成新子类的设计即可,新增体现了多态的拓展性和便利性
多态的弊端
- 不能调用子类特有的功能,由于前面说过,编译时要看左边父类,那么编译时先检查父类中有无该方法,有才能通过编译,这使得子类的特有功能无法通过编译
- 解决方案:使用自动类型转换或强制类型转换变回原来的类型即可使用其特有功能,不过要注意不能转错