1. 什么是多态
多态(Polymorphism)是面向对象三大特征之一。
多态是同一个行为
具有多个不同表现形式
或形态的能力。
先来看一个例子:
- 新建一个动物类 Animal
public class Animal {
// 体重
private int weight;
public void eat(){
System.out.println("动物要吃东西");
}
public void sleep() {
System.out.println("动物要睡觉");
}
}
- 新建一个猫类 Cat,继承动物类,并重写 eat 方法。
public class Cat extends Animal{
public void eat() {
System.out.println("猫要吃老鼠");
}
}
- 新建一个猪类 Pig,继承动物类,并重写 eat 方法。
public class Pig extends Animal{
public void eat() {
System.out.println("猪要吃草");
}
}
- 测试
// 多态测试类
public class PolymorphismTest {
public static void main(String[] args) {
Animal a1 = new Cat();
a1.eat();
Animal a2 = new Pig();
a2.eat();
}
}
运行结果:
上面的例子就是多态,多态就是同一个行为 eat(),作用在不同的对象上(Cat、Pig),会有不同的表现形式(猫吃老鼠,猪吃草)。
也就是说同一操作作用于不同的对象,可以产生不同的效果。
通过上面的例子我们发现了多态的特性:
-
- 子类继承父类
-
- 子类重写父类方法
-
- 父类引用指向了子类的对象(向上转型)
2. 为什么要用多态
继承这一特性告诉我们如果一些类具有一些相同的特征,可以把这些相同的特征提取出来放到一个父类里面,让其他类去继承。
但是后面随着子类的数量越来越多,父类的某些方法已经不能满足一些子类的需求:例如动物吃东西。
所以其他子类需要重写父类的方法,来满足自己特定
的需求。
我们只用编写父类的方法,让子类继承父类即可,不过多关注子类重写方法的代码实现。这样可以大大提高程序的可复用性
。
子类的重写方法可以被父类调用,这叫向后兼容,可以提高代码的可扩充性
和可维护性
。
Animal a1 = new Cat();
a1.eat();
Animal a2 = new Pig();
a2.eat();
例如电脑的 USB 接口,当初设计的时候只是定义了这样一个接口规范,无论是键盘还是鼠标只要遵守这个规范就能使用。
当初设计 USB 接口的时候难道还要考虑鼠标是怎样造出来的?键盘里面有多少个按键吗?难道还要考虑在他们身上都设计一个和电脑一样的 USB 接口才能使用?
如果有几千个外设,那设计电脑的人不得累死?
所以设计电脑的人就只从定义 USB 接口出发,鼠标遵守了这个规范,就能操作电脑系统。键盘遵守了这个规范,就能打字......
所以多态就是子类重写父类方法(相当于子类遵守USB接口规范,实现自己的功能),然后从父类角度出发,父类调用子类重写的方法(相当于连上USB接口,实现不同的功能)。
3. 向上转型和向下转型
java 多态中有两种语法格式:向上转型和向下转型。
3.1 向上转型
向上转型:子类型转换为父类型,就是父类引用
指向子类对象。
向上转型是一种自动转换类型
,比如猫、猪一定属于动物类。所以他们创建的对象可以赋值
(从右向左赋值)给父类的引用,例如:
Animal a1 = new Cat();
Animal a2 = new Pig();
3.2 向下转型
向下转型:父类型转换为子类型,又被叫做强制类型转换
。例如:
public class PolymorphicTest {
public static void main(String[] args) {
Animal animal = new Cat();
Cat cat = (Cat)animal;
cat.eat();
}
}
运行结果:
有的人可能会说,你不是强制类型转换吗?为什么不能这样写:
public class PolymorphicTest {
public static void main(String[] args) {
Animal animal = new Animal();
Cat cat = (Cat)animal;
cat.eat();
}
}
我们看一下运行结果:
Exception in thread "main" java.lang.ClassCastException: com.example.xxl.model.Animal cannot be cast to com.example.xxl.model.Cat
at com.example.xxl.model.PolymorphicTest.main(PolymorphicTest.java:7)
为什么报错了?
因为你第一次新建个动物对象你根本就不知道它是啥动物,所以强转成猫肯定有问题。就好比科学家发现一颗新的星球,你能直接说它是地球或者火星吗?显然不能。
那为什么这段代码没有报错:
Animal animal = new Cat();
Cat cat = (Cat)animal;
cat.eat();
因为 animal 本身指向的就是猫对象,所以它可以向下转型为 Cat。
总结:
- 向上转型:父类引用指向子类对象。
- 向下转型:
在向上转型的基础之上
,再强制转换为子类对象。
4. instanceof 运算符
我们在做向下转型的时候,可能会遇到这种情况:
Animal animal = new Cat();
Pig pig = (Pig)animal;
pig.eat();
运行结果:
Exception in thread "main" java.lang.ClassCastException: com.example.xxl.model.Cat cannot be cast to com.example.xxl.model.Pig
at com.example.xxl.model.PolymorphicTest.main(PolymorphicTest.java:8)
为什么会抛出类型转换的异常?
这是因为在向下转型的过程中,类型不兼容。上面例子中 animal 明明指向的是一只猫,你非要转换成一头猪,强扭的瓜不甜啊!
那怎样解决这种问题?
我们可以在做向下类型转换之前先判断一下转换的类型,这就需要用到 instanceof 了,它的的运算结果是布尔类型。
instanceof 运算符语法格式:
(引用类型指向的对象 instanceof 类型)
例如:
// 多态测试类
public class PolymorphicTest {
public static void main(String[] args) {
Animal animal = new Cat();
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.eat();
}
if(animal instanceof Pig){
Pig pig = (Pig)animal;
pig.eat();
}
}
}
运行结果:
解答:上面的例子中我们用 instanceof 运算符判断 animal 引用指向的对象是不是期望的类型,因为 animal 指向的是一只猫类,所以会执行第一个 if 语句,输出“猫吃老鼠”。
5. 从程序运行层面看多态
我们都知道 java 程序分为编译和运行阶段。
Animal animal = new Cat();
animal.eat();
在编译阶段,animal 的数据类型是 Animal,所以在调用 eat 方法时先去 Animal.class 中找 eat 方法。
当程序运行时,animal 指向了 Cat 类,所以会去调用 Cat 类的 eat 方法,所以执行结果是“猫吃老鼠。”
6. 多态小案例
6.1 电脑连接外部设备
- 新建一个电脑外部设备类 Equipment
// 电脑设备类
public class Equipment {
// 设备名称
String name = "设备";
public void start() {
System.out.println("设备开始启动");
}
public void work() {
System.out.println("设备开始工作了");
}
}
- 新建一个鼠标类 Mouse,继承 Equipment
// 鼠标类
public class Mouse extends Equipment{
String name = "鼠标";
public void work() {
System.out.println("鼠标开始工作了");
}
public void move() {
System.out.println("鼠标开始在电脑窗口移动");
}
}
- 新建一个电脑键盘类 Keyboard,继承 Equipment
// 电脑键盘类
public class Keyboard extends Equipment{
String name = "键盘";
public void work() {
System.out.println("键盘开始工作了");
}
public void print() {
System.out.println("键盘开始打字");
}
}
- 新建一个电脑类
// 电脑类
public class Computer {
public void connect(Equipment equipment){
System.out.println("电脑连接上了:" + equipment.name);
equipment.work();
}
}
- 测试
// 多态测试类
public class PolymorphicTest {
public static void main(String[] args) {
Computer computer = new Computer();
Equipment equipment1 = new Mouse();
equipment1.name ="鼠标";
equipment1.start();
System.out.println("--------------------");
computer.connect(equipment1);
Mouse mouse = (Mouse)equipment1;
mouse.move();
System.out.println("--------------------");
Equipment equipment2 = new Keyboard();
equipment2.name = "键盘";
computer.connect(equipment2);
Keyboard keyboard = (Keyboard)equipment2;
keyboard.print();
}
}
运行结果:
结果分析:
- Mouse
继承
了 Equipment 类,并且重写了 work 方法,所以 computer 对象调用 connect 方法传入 equipment1 对象,调用的是 Mouse类中重写的 work 方法,这就是多态。 - move 方法是 Mouse 中独有的方法,要想调用 move 方法,必须向下转型为 Mouse 对象。这是因为向上转型之后,
多态不使用子类特有的方法
。 - 下面键盘的例子类比。
6.2 花木兰替父从军
- 话说南北朝时期,国家开战,朝廷征兵。花木兰的父亲
花弧
由于年迈不适合参军,儿子又年幼,可是朝廷冷酷无情,花弧一筹莫展。
我们新建一个 HuaHu
类,花弧有自己的姓名、性别、年龄以及“骑马杀敌”的方法。
// 花弧
public class HuaHu {
// 姓名
String name = "花弧";
// 性别
String sex = "男";
// 年龄
int age = 45;
// 骑马杀敌方法
public void kill() {
System.out.println("花弧可以骑马杀敌!!");
}
}
- 花弧的宝贝闺女
花木兰
不但武艺超群,而且箭术无双。木兰不想让老父亲难过,决定替父从军。
我们新建一个 HuaMuLan
类,继承 HuaHu 类。花木兰也有自己的姓名、性别、年龄以及“骑马杀敌”的方法。不过花木兰有自己特有的方法:涂胭脂粉。
// 花木兰类
public class HuaMuLan extends HuaHu{
// 姓名
String name = "花木兰";
// 性别
String sex = "女";
// 年龄
int age = 20;
// 重写他爹骑马杀敌方法
public void kill() {
System.out.println("花木兰骑马杀敌,牛逼 plus!!");
}
// 自己独有的涂胭脂粉的方法
public void makeUp() {
System.out.println("我是花木兰,我爱涂胭脂粉,美美哒!耶耶!");
}
}
- 花木兰女扮男装,替父从军。这就相当于父类的引用(花弧的名字)指向了子类对象(花木兰本人)。这其实就是多态中的
向上转型
。
到了军中,长官:“嘿哥们,做一下自我介绍呗!”
花木兰:“花弧,男,45岁。”
所以在访问子类对象(花木兰)的成员属性(姓名、性别、年龄)时,其实看到的都是花木兰她父亲的名字(花弧)、性别(男)、年龄(45岁)。
当骑马打仗时,花木兰亲自上战场,用自己超强的武艺打败了敌军。
但是这时候,花木兰不能也不敢涂胭脂粉。
我们新建一个测试类:
// 多态测试类
public class PolymorphismTest {
public static void main(String[] args) {
HuaHu huaHu = new HuaMuLan();
System.out.println("姓名:"+huaHu.name);
System.out.println("年龄:"+huaHu.age);
System.out.println("性别"+huaHu.sex);
System.out.println("---------战争开始-----------");
huaHu.kill();
}
}
运行结果:
- 十二年过去了,边疆战乱平定,花木兰跟随大军胜利还朝。
花木兰恢复了女儿身。别人问她是谁,木兰说:“我叫花木兰,女,今年20岁”。这时候,花木兰终于可以涂胭脂粉了。
这其实就是多态中的向下转型
。
我们新建一个测试类:
// 多态测试类
public class PolymorphismTest {
public static void main(String[] args) {
HuaHu huaHu = new HuaMuLan();
System.out.println("姓名:"+huaHu.name);
System.out.println("年龄:"+huaHu.age);
System.out.println("性别"+huaHu.sex);
System.out.println("---------战争开始-----------");
huaHu.kill();
System.out.println("---------战争结束-----------");
HuaMuLan huaMuLan = (HuaMuLan)huaHu;
System.out.println("姓名:"+huaMuLan.name);
System.out.println("年龄:"+huaMuLan.age);
System.out.println("性别"+huaMuLan.sex);
huaMuLan.makeUp();
}
}
运行结果:
总结:
-
- 向上转型,父类引用调用的方法是子类重写的方法。
-
- 向上转型,父类引用获取的属性是父类中的属性。
-
- 向上转型,父类引用不能获取子类独有的方法。
-
- 向下转型,子类可以获取自己独有的属性和方法。