一 多态的定义和多态中的相关概念
多态中涉及到的概念
- 向上转型:子类型-->父类型,父类型的引用指向子类型的对象,自动类型转换
- 向下转型:父类型-->子类型,强制类型转换
- 不管是向上转型还是向下转型,这两者之间必须要有继承关系。如果没有继承关系,编译是无法通过的。
多态的概念
- 在Java中,父类型的引用指向子类型的对象这种机制,导致程序存在编译阶段的静态绑定和运行阶段的动态绑定两种不同的形态,这种机制称为多态语法机制
二 案例讲解多态
class Animal { //动物类
public void move() { System.out.println("动物在移动"); }
}
class Cat extends Animal { //猫类继承动物类
@Override
public void move() { System.out.println("猫在走猫步"); }
public void catchMouse() { System.out.println("猫抓老鼠"); }
}
class Bird extends Animal{ //鸟类继承动物类
@Override
public void move() { System.out.println("鸟儿在飞翔"); }
}
public class Test {
public static void main(String[] args) {
/*
* Animal和Cat之间存在继承关系,Animal是父类,Cat是子类。
* new Cat()创建的是一个Cat类型的对象。a1是一个Animal类型的引用,可见他们之间进行了类型转换。
* 子类型转换为父类型,称为向上类型转换/upcasting
* Java中允许父类型的引用指向子类型的对象
*/
Animal a1=new Bird();
Animal a2=new Cat();
a2.move(); //输出结果是“猫在走猫步”
a2.catchMouse(); //编译不通过
}
}
上述使用多态机制的程序的内存分析图
语句解析:
- Java程序永远分为编译阶段和运行阶段
- 在测试类中,Animal a2=new Cat() 能编译通过,且能执行。因为 Animal和Cat之间存在继承关系,Animal是父类,Cat是子类。new Cat()创建的是一个Cat类型的对象。a1是一个Animal类型的引用,可见他们之间进行了类型转换。子类型转换为父类型,称为向上类型转换/upcasting,Java中允许父类型的引用指向子类型的对象
- 在测试类中,a2.move()能够编译通过,且能够执行。这是因为:编译阶段,编译器分析a2是一个Animal类型的引用,则去Animal.class中去查看是否有move()方法,有,则编译通过,这个过程我们称为静态绑定或编译阶段绑定;在运行阶段,JVM中在堆内存中真实创建的对象是Cat类型的对象,那么这句代码在运行过程中一定会调用Cat对象的move()方法,此时,程序中发生了程序的动态绑定,又称运行阶段绑定。
- 在测试类中,a2.catchMouse()不能编译通过,也不能顺利执行。这是因为:在编译阶段,编译器分析a2是一个Animal类型的引用,则去Animal.class中去查看是否有catchMouse()方法,发现不存在,则编译阶段的动态绑定失败,编译失败;编译失败更不可能运行了。
三 向下转型(解决上述a2.catchMouse()编译不通过的问题)
1. 向下转型的定义
父类-->子类。向下转型也需要两者之间有继承关系,不然编译报错。向下转型需要强制类型转换。
2. 什么时候需要向下转型
当父类类型的引用需要调用子类的方法,且该方法只存在子类中,不存在父类中时,必须进行向下转型。
比如上述例子中a2.catchMouse()编译不能通过。可以先将a2向下转型,转化为Cat型,再调用catchMouse()方法
public class Test {
public static void main(String[] args) {
Animal a2 = new Cat();
a2.move(); // 输出结果是“猫在走猫步”
Cat c = (Cat) a2;
c.catchMouse(); // 运行结果是“猫抓老鼠”
}
}
我们需要注意下面的代码,可以编译通过,但是会报一个java.lang.ClassCastException异常。这是因为:Animal a=new Bird()属于向上转型,肯定是可以的,也能顺利运行。而对于 Cat c=(Cat)a,在编译阶段,编译器首先能分析到a是一个Animal类型的引用,Cat类是Animal类的子类,Animal类型的引用a也能向下转型为Cat类型,但是在运行阶段会出现ClassCastException异常,因为在JVM中真实存在的对象是Bird类型的,而Bird类和Cat类不存在继承关系,不能进行类型转换。
public class Test {
public static void main(String[] args) {
Animal a=new Bird();
Cat c=(Cat)a; //会报java.lang.ClassCastException异常
}
}
3. 向下转型时,怎么避免ClassCastException异常
- 使用instanceof运算符可以避免ClassCastException异常。
- instanceof的语法:引用 instanceof 类型
- instanceof运算符的结果只有两种可能true or false. a instanceof Animal(如果a这个引用指向的对象是Animal类型的,则结果为true,否则为false),下面有案例
- 在Java规范中要求:在进行向下转型前先使用instanceof判断下类型避免ClassCastException
public class Test {
public static void main(String[] args) {
Animal a=new Bird();
if (a instanceof Cat) {
Cat c=(Cat)a;
}else if (a instanceof Bird) {
Bird b=(Bird)a;
}
}
}
四 多态在实际开发中的作用
1. 不使用多态的案例
class Master {
public void feed(Bird b) { b.eat(); }
}
class Bird{
public void eat() { System.out.println("鸟儿在吃食"); }
}
public class Test {
public static void main(String[] args) {
Master master=new Master();
Bird b=new Bird();
master.feed(b); //输出“鸟儿在吃食”
}
}
在上述代码中,有一个Master类,主人可以喂鸟;有一个Bird类,鸟可以吃食。在测试类中完成了主人喂鸟的过程。
假如有一天,主人又想养一只猫,那么我们不仅要创建一个猫类,还要到Master类中去修改代码添加一个喂猫的重载方法。如下:
class Cat {
public void eat() { System.out.println("猫在吃食"); }
}
public class Master {
public void feed(Bird b) { b.eat(); }
public void feed(Cat c) { c.eat(); }
}
这样有一个缺陷:Master类和Bird类和Cat类的耦合程度高,Master类的扩展性差,只要再增加一个类,那么就必须修改Master类,这是非常不合理的。这种问题就可以使用多态来解决。
2. 多态解决上述问题
class Master {
public void feed(Pet a) { a.eat(); }
}
class Pet {
public void eat() { System.out.println("宠物在吃东西"); }
}
class Cat extends Pet {
@Override
public void eat() { System.out.println("猫在吃食"); }
}
class Bird extends Pet{
@Override
public void eat() { System.out.println("鸟儿在吃食"); }
}
public class Test {
public static void main(String[] args) {
Master master=new Master();
Bird b=new Bird();
Cat c=new Cat();
master.feed(b); //输出“鸟在吃食”
master.feed(c); //输出“猫在吃食”
}
}
使用这种多态的方法,不管加多少个其他类型的宠物,都不需要修改Maste类,降低了耦合度,提高了程序的可扩展性。
3. 多态的优点
降低程序的耦合度,增强程序的扩展能力